diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index b2c53776ad4..bbb76d3675c 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -5,8 +5,8 @@
"build": {
"dockerfile": "Dockerfile",
"args": {
- // Update the VARIANT arg to pick a version of Go: 1, 1.15, 1.14
- "VARIANT": "1.14",
+ // Update the VARIANT arg to pick a version of Go
+ "VARIANT": "1.16",
// Options
"INSTALL_NODE": "false",
"NODE_VERSION": "lts/*",
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fb9b6592308..a14596263c3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -3,7 +3,7 @@ name: Release
on:
push:
tags:
- - '[0-9]+.[0-9]+.[0-9]+'
+ - 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
release:
@@ -13,13 +13,15 @@ jobs:
steps:
- name: Get Version
id: get_version
- run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\//}
+ run: |
+ echo ::set-output name=tag::${GITHUB_REF/refs\/tags\/}
+ echo ::set-output name=version::${GITHUB_REF/refs\/tags\/v}
- name: Create & Publish Release
uses: release-drafter/release-drafter@v5.12.1
with:
name: ${{ steps.get_version.outputs.version }}
- tag: ${{ steps.get_version.outputs.version }}
+ tag: ${{ steps.get_version.outputs.tag }}
version: ${{ steps.get_version.outputs.version }}
publish: true
env:
diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml
index 30370178ca8..9cf371ca168 100644
--- a/.github/workflows/validate-merge.yml
+++ b/.github/workflows/validate-merge.yml
@@ -12,7 +12,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
- go-version: 1.14.2
+ go-version: 1.16.4
- name: Checkout Merged Branch
uses: actions/checkout@v2
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 3efc51d287a..1eb137467ec 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -10,7 +10,7 @@ jobs:
validate:
strategy:
matrix:
- go-version: [1.14.x, 1.15.x]
+ go-version: [1.15.x, 1.16.x]
os: [ubuntu-18.04]
runs-on: ${{ matrix.os }}
diff --git a/Dockerfile b/Dockerfile
index d76398dc6d3..defb64c8586 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.14.2.linux-amd64.tar.gz && \
- tar -xf go1.14.2.linux-amd64.tar.gz && \
+ wget https://dl.google.com/go/go1.16.4.linux-amd64.tar.gz && \
+ tar -xf go1.16.4.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 9189421bd9d..8d40cafc6ce 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Please consider [registering your Prebid Server](https://docs.prebid.org/prebid-
## Installation
-First install [Go](https://golang.org/doc/install) version 1.14 or newer.
+First install [Go](https://golang.org/doc/install) version 1.15 or newer.
Note that prebid-server is using [Go modules](https://blog.golang.org/using-go-modules).
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`.
@@ -50,16 +50,19 @@ go build .
Load the landing page in your browser at `http://localhost:8000/`.
For the full API reference, see [the endpoint documentation](https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html)
+## Go Modules
+
+The packages within this repository are intended to be used as part of the Prebid Server compiled binary. If you
+choose to import Prebid Server packages in other projects, please understand we make no promises on the stability
+of exported types.
## Contributing
Want to [add an adapter](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html)? Found a bug? Great!
-Report bugs, request features, and suggest improvements [on Github](https://github.com/PubMatic-OpenWrap/prebid-server/issues).
-
Or better yet, [open a pull request](https://github.com/PubMatic-OpenWrap/prebid-server/compare) with the changes you'd like to see.
-## IDE Setup for PBS-Go development
+## IDE Recommendations
-The quickest way to start developing PBS-Go in a reproducible environment isolated from your host OS
-is by using this [VScode Remote Container Setup](devcontainer.md)
+The quickest way to start developing Prebid Server in a reproducible environment isolated from your host OS
+is by using Visual Studio Code with [Remote Container Setup](devcontainer.md).
diff --git a/adapters/adagio/adagio.go b/adapters/adagio/adagio.go
new file mode 100644
index 00000000000..0da4d6ac9e4
--- /dev/null
+++ b/adapters/adagio/adagio.go
@@ -0,0 +1,139 @@
+package adagio
+
+import (
+ "bytes"
+ "compress/gzip"
+ "encoding/json"
+ "fmt"
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/openrtb_ext"
+ "net/http"
+)
+
+// Builder builds a new instance of the Adagio adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+ bidder := &adapter{
+ endpoint: config.Endpoint,
+ }
+ return bidder, nil
+}
+
+type adapter struct {
+ endpoint string
+}
+
+type extBid struct {
+ Prebid *openrtb_ext.ExtBidPrebid
+}
+
+// MakeRequests prepares the HTTP requests which should be made to fetch bids.
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ json, err := json.Marshal(request)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ if request.Device != nil {
+ if len(request.Device.IPv6) > 0 {
+ headers.Add("X-Forwarded-For", request.Device.IPv6)
+ }
+ if len(request.Device.IP) > 0 {
+ headers.Add("X-Forwarded-For", request.Device.IP)
+ }
+ }
+
+ if request.Test == 0 {
+ // Gzip the body
+ // Note: Gzipping could be handled natively later: https://github.com/prebid/prebid-server/issues/1812
+ var bodyBuf bytes.Buffer
+ gz := gzip.NewWriter(&bodyBuf)
+ if _, err = gz.Write(json); err == nil {
+ if err = gz.Close(); err == nil {
+ json = bodyBuf.Bytes()
+ headers.Add("Content-Encoding", "gzip")
+ // /!\ Go already sets the `Accept-Encoding: gzip` header. Never add it manually, or Go won't decompress the response.
+ //headers.Add("Accept-Encoding", "gzip")
+ }
+ }
+ }
+
+ requestToBidder := &adapters.RequestData{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: json,
+ Headers: headers,
+ }
+
+ return []*adapters.RequestData{requestToBidder}, nil
+}
+
+const unexpectedStatusCodeFormat = "Unexpected status code: %d. Run with request.debug = 1 for more info"
+
+// MakeBids unpacks the server's response into Bids.
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ switch response.StatusCode {
+ case http.StatusOK:
+ break
+ case http.StatusNoContent:
+ return nil, nil
+ case http.StatusServiceUnavailable:
+ fallthrough
+ case http.StatusBadRequest:
+ fallthrough
+ case http.StatusUnauthorized:
+ fallthrough
+ case http.StatusForbidden:
+ err := &errortypes.BadInput{
+ Message: fmt.Sprintf(unexpectedStatusCodeFormat, response.StatusCode),
+ }
+ return nil, []error{err}
+ default:
+ err := &errortypes.BadServerResponse{
+ Message: fmt.Sprintf(unexpectedStatusCodeFormat, response.StatusCode),
+ }
+ return nil, []error{err}
+ }
+
+ var openRTBBidderResponse openrtb2.BidResponse
+ if err := json.Unmarshal(response.Body, &openRTBBidderResponse); err != nil {
+ return nil, []error{err}
+ }
+
+ bidsCapacity := len(internalRequest.Imp)
+ errs := make([]error, 0, bidsCapacity)
+ bidderResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity)
+ var typedBid *adapters.TypedBid
+ for _, seatBid := range openRTBBidderResponse.SeatBid {
+ for _, bid := range seatBid.Bid {
+ activeBid := bid
+
+ activeExt := &extBid{}
+ if err := json.Unmarshal(activeBid.Ext, activeExt); err != nil {
+ errs = append(errs, err)
+ }
+
+ var bidType openrtb_ext.BidType
+ if activeExt.Prebid != nil && activeExt.Prebid.Type != "" {
+ bidType = activeExt.Prebid.Type
+ } else {
+ err := &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Failed to find native/banner/video mediaType \"%s\" ", activeBid.ImpID),
+ }
+ errs = append(errs, err)
+ continue
+ }
+
+ typedBid = &adapters.TypedBid{Bid: &activeBid, BidType: bidType}
+ bidderResponse.Bids = append(bidderResponse.Bids, typedBid)
+ }
+ }
+
+ return bidderResponse, nil
+}
diff --git a/adapters/adagio/adagio_test.go b/adapters/adagio/adagio_test.go
new file mode 100644
index 00000000000..d5e25c7836d
--- /dev/null
+++ b/adapters/adagio/adagio_test.go
@@ -0,0 +1,75 @@
+package adagio
+
+import (
+ "encoding/json"
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/stretchr/testify/assert"
+ "testing"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func buildFakeBidRequest() openrtb2.BidRequest {
+ imp1 := openrtb2.Imp{
+ ID: "some-impression-id",
+ Banner: &openrtb2.Banner{},
+ Ext: json.RawMessage(`{"bidder": {"organizationId": "1000", "site": "site-name", "placement": "ban_atf"}}`),
+ }
+
+ fakeBidRequest := openrtb2.BidRequest{
+ ID: "some-request-id",
+ Imp: []openrtb2.Imp{imp1},
+ }
+
+ return fakeBidRequest
+}
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{
+ Endpoint: "http://localhost/prebid_server"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "adagiotest", bidder)
+}
+
+func TestMakeRequests_NoGzip(t *testing.T) {
+ fakeBidRequest := buildFakeBidRequest()
+ fakeBidRequest.Test = 1 // Do not use Gzip in Test Mode.
+
+ bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{
+ Endpoint: "http://localhost/prebid_server"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ requestData, errs := bidder.MakeRequests(&fakeBidRequest, nil)
+
+ assert.Nil(t, errs)
+ assert.Equal(t, 1, len(requestData))
+
+ body := &openrtb2.BidRequest{}
+ err := json.Unmarshal(requestData[0].Body, body)
+ assert.NoError(t, err, "Request body unmarshalling error should be nil")
+ assert.Equal(t, 1, len(body.Imp))
+}
+
+func TestMakeRequests_Gzip(t *testing.T) {
+ fakeBidRequest := buildFakeBidRequest()
+
+ bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{
+ Endpoint: "http://localhost/prebid_server"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ requestData, errs := bidder.MakeRequests(&fakeBidRequest, nil)
+ assert.Empty(t, errs, "Got errors while making requests")
+ assert.Equal(t, []string{"gzip"}, requestData[0].Headers["Content-Encoding"])
+}
diff --git a/adapters/adagio/adagiotest/exemplary/banner-web.json b/adapters/adagio/adagiotest/exemplary/banner-web.json
new file mode 100644
index 00000000000..732b40d2c1d
--- /dev/null
+++ b/adapters/adagio/adagiotest/exemplary/banner-web.json
@@ -0,0 +1,155 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w":320,
+ "h":50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w":320,
+ "h":50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 320,
+ "h": 50,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ }
+ ],
+ "seat": "adagio"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "adagio": 154
+ },
+ "tmaxrequest": 1000
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids":[
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "crid": "20",
+ "adomain": [
+ "awesome.com"
+ ],
+ "w": 320,
+ "h": 50,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/adagio/adagiotest/exemplary/multi-format.json b/adapters/adagio/adagiotest/exemplary/multi-format.json
new file mode 100644
index 00000000000..85e0be26131
--- /dev/null
+++ b/adapters/adagio/adagiotest/exemplary/multi-format.json
@@ -0,0 +1,165 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "multi-format-id",
+ "banner": {
+ "w":320,
+ "h":50
+ },
+ "video": {
+ "mimes": ["video\/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "multi-format-id",
+ "banner": {
+ "w":320,
+ "h":50
+ },
+ "video": {
+ "mimes": ["video\/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "multi-format-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 320,
+ "h": 50,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ }
+ ],
+ "seat": "adagio"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "adagio": 154
+ },
+ "tmaxrequest": 1000
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids":[
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "multi-format-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "crid": "20",
+ "adomain": [
+ "awesome.com"
+ ],
+ "w": 320,
+ "h": 50,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/adagio/adagiotest/exemplary/multi-imp.json b/adapters/adagio/adagiotest/exemplary/multi-imp.json
new file mode 100644
index 00000000000..66af28ea559
--- /dev/null
+++ b/adapters/adagio/adagiotest/exemplary/multi-imp.json
@@ -0,0 +1,222 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "banner-impression-id",
+ "banner": {
+ "w":320,
+ "h":50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ },
+ {
+ "id": "video-impression-id",
+ "video": {
+ "mimes": ["video\/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "in_article"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "banner-impression-id",
+ "banner": {
+ "w":320,
+ "h":50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ },
+ {
+ "id": "video-impression-id",
+ "video": {
+ "mimes": ["video\/mp4"],
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "in_article"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "banner-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 320,
+ "h": 50,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ },
+ {
+ "id": "b4ae1b4e2fc24a4fb45540082e98e162",
+ "impid": "video-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ }
+ ],
+ "seat": "adagio"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "adagio": 154
+ },
+ "tmaxrequest": 1000
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids":[
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "banner-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "crid": "20",
+ "adomain": [
+ "awesome.com"
+ ],
+ "w": 320,
+ "h": 50,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "b4ae1b4e2fc24a4fb45540082e98e162",
+ "impid": "video-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "crid": "20",
+ "adomain": [
+ "awesome.com"
+ ],
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/adagio/adagiotest/exemplary/native-web.json b/adapters/adagio/adagiotest/exemplary/native-web.json
new file mode 100644
index 00000000000..0085aaddc64
--- /dev/null
+++ b/adapters/adagio/adagiotest/exemplary/native-web.json
@@ -0,0 +1,151 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native": {
+ "ver":"1.1",
+ "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "in_article"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Forwarded-For": [
+ "2607:fb90:f27:4512:d800:cb23:a603:e245"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native": {
+ "ver":"1.1",
+ "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "in_article"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "ext": {
+ "prebid": {
+ "type": "native"
+ }
+ }
+ }
+ ],
+ "seat": "adagio"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "acuityads": 154
+ },
+ "tmaxrequest": 1000
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids":[
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "crid": "20",
+ "adomain": [
+ "awesome.com"
+ ],
+ "ext": {
+ "prebid": {
+ "type": "native"
+ }
+ }
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/adagio/adagiotest/exemplary/video-web.json b/adapters/adagio/adagiotest/exemplary/video-web.json
new file mode 100644
index 00000000000..353420ee962
--- /dev/null
+++ b/adapters/adagio/adagiotest/exemplary/video-web.json
@@ -0,0 +1,175 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video\/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "in_article"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video\/mp4"
+ ],
+ "minduration": 120,
+ "maxduration": 150,
+ "w": 640,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "in_article"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 1280,
+ "h": 720,
+ "ext": {
+ "adagio": {
+ "outstream": {
+ "bvwUrl": "https://cool.io"
+ }
+ },
+ "prebid": {
+ "type": "video"
+ }
+ }
+ }
+ ],
+ "seat": "adagio"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "adagio": 154
+ },
+ "tmaxrequest": 1000
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids": [
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 1280,
+ "h": 720,
+ "ext": {
+ "adagio": {
+ "outstream": {
+ "bvwUrl": "https://cool.io"
+ }
+ },
+ "prebid": {
+ "type": "video"
+ }
+ }
+ },
+ "type":"video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/params/race/banner.json b/adapters/adagio/adagiotest/params/race/banner.json
new file mode 100644
index 00000000000..66694466d84
--- /dev/null
+++ b/adapters/adagio/adagiotest/params/race/banner.json
@@ -0,0 +1,5 @@
+{
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+}
diff --git a/adapters/adagio/adagiotest/params/race/native.json b/adapters/adagio/adagiotest/params/race/native.json
new file mode 100644
index 00000000000..4affd5b232c
--- /dev/null
+++ b/adapters/adagio/adagiotest/params/race/native.json
@@ -0,0 +1,5 @@
+{
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "in_article"
+}
diff --git a/adapters/adagio/adagiotest/params/race/video.json b/adapters/adagio/adagiotest/params/race/video.json
new file mode 100644
index 00000000000..4affd5b232c
--- /dev/null
+++ b/adapters/adagio/adagiotest/params/race/video.json
@@ -0,0 +1,5 @@
+{
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "in_article"
+}
diff --git a/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json b/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json
new file mode 100644
index 00000000000..7b1267f6d50
--- /dev/null
+++ b/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json
@@ -0,0 +1,130 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "awesome-user"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 320,
+ "h": 50,
+ "ext": {}
+ }
+ ],
+ "seat": "adagio"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ "responsetimemillis": {
+ "adagio": 154
+ },
+ "tmaxrequest": 1000
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids": []
+ }
+ ]
+}
diff --git a/adapters/adagio/adagiotest/supplemental/status-204.json b/adapters/adagio/adagiotest/supplemental/status-204.json
new file mode 100644
index 00000000000..4d604a01fb9
--- /dev/null
+++ b/adapters/adagio/adagiotest/supplemental/status-204.json
@@ -0,0 +1,62 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/adagio/adagiotest/supplemental/status-400.json b/adapters/adagio/adagiotest/supplemental/status-400.json
new file mode 100644
index 00000000000..093c5458c0a
--- /dev/null
+++ b/adapters/adagio/adagiotest/supplemental/status-400.json
@@ -0,0 +1,67 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 400,
+ "body": "bad request"
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/supplemental/status-401.json b/adapters/adagio/adagiotest/supplemental/status-401.json
new file mode 100644
index 00000000000..a33aca203d0
--- /dev/null
+++ b/adapters/adagio/adagiotest/supplemental/status-401.json
@@ -0,0 +1,67 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 401,
+ "body": "unauthorized"
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 401. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/supplemental/status-403.json b/adapters/adagio/adagiotest/supplemental/status-403.json
new file mode 100644
index 00000000000..59c5a9cbf6b
--- /dev/null
+++ b/adapters/adagio/adagiotest/supplemental/status-403.json
@@ -0,0 +1,67 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 403,
+ "body": "forbidden"
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 403. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/supplemental/status-500.json b/adapters/adagio/adagiotest/supplemental/status-500.json
new file mode 100644
index 00000000000..0077d7457f9
--- /dev/null
+++ b/adapters/adagio/adagiotest/supplemental/status-500.json
@@ -0,0 +1,68 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "debug": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 500,
+ "body": "internal error"
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adagio/adagiotest/supplemental/status-503.json b/adapters/adagio/adagiotest/supplemental/status-503.json
new file mode 100644
index 00000000000..d2a52893de5
--- /dev/null
+++ b/adapters/adagio/adagiotest/supplemental/status-503.json
@@ -0,0 +1,67 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 503,
+ "body": "Service unavailable"
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 503. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adagio/adagiotest/supplemental/status-504.json b/adapters/adagio/adagiotest/supplemental/status-504.json
new file mode 100644
index 00000000000..1b779d5c83f
--- /dev/null
+++ b/adapters/adagio/adagiotest/supplemental/status-504.json
@@ -0,0 +1,67 @@
+{
+ "mockBidRequest": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "http://localhost/prebid_server",
+ "body": {
+ "test": 1,
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "organizationId": "1000",
+ "site": "site-name",
+ "placement": "ban_atf"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 504,
+ "body": "gateway timeout"
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 504. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/adagio/params_test.go b/adapters/adagio/params_test.go
new file mode 100644
index 00000000000..ee8f702e451
--- /dev/null
+++ b/adapters/adagio/params_test.go
@@ -0,0 +1,57 @@
+package adagio
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+var validParams = []string{
+ `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf" }`,
+ `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "_unknown": "ban_atf"}`,
+ `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "features": {"a": "a", "b": "b"}}`,
+}
+
+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.BidderAdagio, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected Adagio params: %s", validParam)
+ }
+ }
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `4.2`,
+ `[]`,
+ `{}`,
+ `{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`,
+ `{ "organizationId": "", "site": "", "placement": "" }`,
+ `{ "organizationId": "", "site": "2", "placement": "3" }`,
+ `{ "organizationId": "1", "site": "", "placement": "3" }`,
+ `{ "organizationId": "1", "site": "2", "placement": "" }`,
+ `{ "organizationId": 1, "site": "2", "placement": "3" }`,
+ `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "features": {"a": "a", "notastring": true}}`,
+}
+
+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.BidderAdagio, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
diff --git a/adapters/adagio/usersync.go b/adapters/adagio/usersync.go
new file mode 100644
index 00000000000..a7230feaada
--- /dev/null
+++ b/adapters/adagio/usersync.go
@@ -0,0 +1,12 @@
+package adagio
+
+import (
+ "text/template"
+
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/usersync"
+)
+
+func NewAdagioSyncer(tmpl *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("adagio", tmpl, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/adagio/usersync_test.go b/adapters/adagio/usersync_test.go
new file mode 100644
index 00000000000..cd4195e16df
--- /dev/null
+++ b/adapters/adagio/usersync_test.go
@@ -0,0 +1,34 @@
+package adagio
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/prebid/prebid-server/privacy"
+ "github.com/prebid/prebid-server/privacy/ccpa"
+ "github.com/prebid/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAdagioSyncer(t *testing.T) {
+ syncURL := "https://mp.4dex.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadagio%26uid%3D%5BUID%5D"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewAdagioSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "0",
+ Consent: "ANDFJDS",
+ },
+ CCPA: ccpa.Policy{
+ Consent: "1-YY",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "https://mp.4dex.io/sync?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redirect=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadagio%26uid%3D%5BUID%5D", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go
index 95319f1e328..1ec2fc08d86 100644
--- a/adapters/adapterstest/test_json.go
+++ b/adapters/adapterstest/test_json.go
@@ -7,8 +7,10 @@ import (
"regexp"
"testing"
+ "github.com/mitchellh/copystructure"
"github.com/mxmCherry/openrtb/v15/openrtb2"
"github.com/prebid/prebid-server/adapters"
+ "github.com/stretchr/testify/assert"
"github.com/yudai/gojsondiff"
"github.com/yudai/gojsondiff/formatter"
@@ -55,6 +57,7 @@ func RunJSONBidderTest(t *testing.T, rootDir string, bidder adapters.Bidder) {
runTests(t, fmt.Sprintf("%s/supplemental", rootDir), bidder, true, false, false)
runTests(t, fmt.Sprintf("%s/amp", rootDir), bidder, true, true, false)
runTests(t, fmt.Sprintf("%s/video", rootDir), bidder, false, false, true)
+ runTests(t, fmt.Sprintf("%s/videosupplemental", rootDir), bidder, true, false, true)
}
// runTests runs all the *.json files in a directory. If allowErrors is false, and one of the test files
@@ -107,24 +110,10 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd
} else if isVideoTest {
reqInfo.PbsEntryPoint = "video"
}
- actualReqs, errs := bidder.MakeRequests(&spec.BidRequest, &reqInfo)
- diffErrorLists(t, fmt.Sprintf("%s: MakeRequests", filename), errs, spec.MakeRequestErrors)
- diffHttpRequestLists(t, filename, actualReqs, spec.HttpCalls)
- bidResponses := make([]*adapters.BidderResponse, 0)
+ requests := testMakeRequestsImpl(t, filename, spec, bidder, &reqInfo)
- var bidsErrs = make([]error, 0, len(spec.MakeBidsErrors))
- for i := 0; i < len(actualReqs); i++ {
- thisBidResponse, theseErrs := bidder.MakeBids(&spec.BidRequest, spec.HttpCalls[i].Request.ToRequestData(t), spec.HttpCalls[i].Response.ToResponseData(t))
- bidsErrs = append(bidsErrs, theseErrs...)
- bidResponses = append(bidResponses, thisBidResponse)
- }
-
- diffErrorLists(t, fmt.Sprintf("%s: MakeBids", filename), bidsErrs, spec.MakeBidsErrors)
-
- for i := 0; i < len(spec.BidResponses); i++ {
- diffBidLists(t, filename, bidResponses[i], spec.BidResponses[i].Bids)
- }
+ testMakeBidsImpl(t, filename, spec, bidder, requests)
}
type testSpec struct {
@@ -194,8 +183,8 @@ type expectedBid struct {
//
// Marshalling the structs and then using a JSON-diff library isn't great either, since
-// diffHttpRequests compares the actual http requests to the expected ones.
-func diffHttpRequestLists(t *testing.T, filename string, actual []*adapters.RequestData, expected []httpCall) {
+// assertMakeRequestsOutput compares the actual http requests to the expected ones.
+func assertMakeRequestsOutput(t *testing.T, filename string, actual []*adapters.RequestData, expected []httpCall) {
t.Helper()
if len(expected) != len(actual) {
@@ -206,7 +195,7 @@ func diffHttpRequestLists(t *testing.T, filename string, actual []*adapters.Requ
}
}
-func diffErrorLists(t *testing.T, description string, actual []error, expected []testSpecExpectedError) {
+func assertErrorList(t *testing.T, description string, actual []error, expected []testSpecExpectedError) {
t.Helper()
if len(expected) != len(actual) {
@@ -227,10 +216,10 @@ func diffErrorLists(t *testing.T, description string, actual []error, expected [
}
}
-func diffBidLists(t *testing.T, filename string, response *adapters.BidderResponse, expected []expectedBid) {
+func assertMakeBidsOutput(t *testing.T, filename string, bidderResponse *adapters.BidderResponse, expected []expectedBid) {
t.Helper()
- if (response == nil || len(response.Bids) == 0) != (len(expected) == 0) {
+ if (bidderResponse == nil || len(bidderResponse.Bids) == 0) != (len(expected) == 0) {
if len(expected) == 0 {
t.Fatalf("%s: expectedBidResponses indicated a nil response, but mockResponses supplied a non-nil response", filename)
}
@@ -239,17 +228,15 @@ func diffBidLists(t *testing.T, filename string, response *adapters.BidderRespon
}
// Expected nil response - give diffBids something to work with.
- if response == nil {
- response = new(adapters.BidderResponse)
+ if bidderResponse == nil {
+ bidderResponse = new(adapters.BidderResponse)
}
- actual := response.Bids
-
- if len(actual) != len(expected) {
- t.Fatalf("%s: MakeBids returned wrong bid count. Expected %d, got %d", filename, len(expected), len(actual))
+ if len(bidderResponse.Bids) != len(expected) {
+ t.Fatalf("%s: MakeBids returned wrong bid count. Expected %d, got %d", filename, len(expected), len(bidderResponse.Bids))
}
- for i := 0; i < len(actual); i++ {
- diffBids(t, fmt.Sprintf("%s: typedBid[%d]", filename, i), actual[i], &(expected[i]))
+ for i := 0; i < len(bidderResponse.Bids); i++ {
+ diffBids(t, fmt.Sprintf("%s: typedBid[%d]", filename, i), bidderResponse.Bids[i], &(expected[i]))
}
}
@@ -331,3 +318,78 @@ func diffJson(t *testing.T, description string, actual []byte, expected []byte)
}
}
}
+
+// testMakeRequestsImpl asserts the resulting values of the bidder's `MakeRequests()` implementation
+// against the expected JSON-defined results and ensures we do not encounter data races in the process.
+// To assert no data races happen we make use of:
+// 1) A shallow copy of the unmarshalled openrtb2.BidRequest that will provide reference values to
+// shared memory that we don't want the adapters' implementation of `MakeRequests()` to modify.
+// 2) A deep copy that will preserve the original values of all the fields. This copy remains untouched
+// by the adapters' processes and serves as reference of what the shared memory values should still
+// be after the `MakeRequests()` call.
+func testMakeRequestsImpl(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, reqInfo *adapters.ExtraRequestInfo) []*adapters.RequestData {
+ t.Helper()
+
+ deepBidReqCopy, shallowBidReqCopy, err := getDataRaceTestCopies(&spec.BidRequest)
+ assert.NoError(t, err, "Could not create request copies. %s", filename)
+
+ // Run MakeRequests
+ requests, errs := bidder.MakeRequests(&spec.BidRequest, reqInfo)
+
+ // Compare MakeRequests actual output versus expected values found in JSON file
+ assertErrorList(t, fmt.Sprintf("%s: MakeRequests", filename), errs, spec.MakeRequestErrors)
+ assertMakeRequestsOutput(t, filename, requests, spec.HttpCalls)
+
+ // Assert no data races occur using original bidRequest copies of references and values
+ assert.Equal(t, deepBidReqCopy, shallowBidReqCopy, "Data race found. Test: %s", filename)
+
+ return requests
+}
+
+// getDataRaceTestCopies returns a deep copy and a shallow copy of the original bidRequest that will get
+// compared to verify no data races occur.
+func getDataRaceTestCopies(original *openrtb2.BidRequest) (*openrtb2.BidRequest, *openrtb2.BidRequest, error) {
+ cpy, err := copystructure.Copy(original)
+ if err != nil {
+ return nil, nil, err
+ }
+ deepReqCopy := cpy.(*openrtb2.BidRequest)
+
+ shallowReqCopy := *original
+
+ // Prebid Server core makes shallow copies of imp elements and adapters are allowed to make changes
+ // to them. Therefore, we need shallow copies of Imp elements here so our test replicates that
+ // functionality and only fail when actual shared momory gets modified.
+ if original.Imp != nil {
+ shallowReqCopy.Imp = make([]openrtb2.Imp, len(original.Imp))
+ copy(shallowReqCopy.Imp, original.Imp)
+ }
+
+ return deepReqCopy, &shallowReqCopy, nil
+}
+
+// testMakeBidsImpl asserts the results of the bidder MakeBids implementation against the expected JSON-defined results
+func testMakeBidsImpl(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, makeRequestsOut []*adapters.RequestData) {
+ t.Helper()
+
+ bidResponses := make([]*adapters.BidderResponse, 0)
+ var bidsErrs = make([]error, 0, len(spec.MakeBidsErrors))
+
+ // We should have as many bids as number of adapters.RequestData found in MakeRequests output
+ for i := 0; i < len(makeRequestsOut); i++ {
+ // Run MakeBids with JSON refined spec.HttpCalls info that was asserted to match MakeRequests
+ // output inside testMakeRequestsImpl
+ thisBidResponse, theseErrs := bidder.MakeBids(&spec.BidRequest, spec.HttpCalls[i].Request.ToRequestData(t), spec.HttpCalls[i].Response.ToResponseData(t))
+
+ bidsErrs = append(bidsErrs, theseErrs...)
+ bidResponses = append(bidResponses, thisBidResponse)
+ }
+
+ // Assert actual errors thrown by MakeBids implementation versus expected JSON-defined spec.MakeBidsErrors
+ assertErrorList(t, fmt.Sprintf("%s: MakeBids", filename), bidsErrs, spec.MakeBidsErrors)
+
+ // Assert MakeBids implementation BidResponses with expected JSON-defined spec.BidResponses[i].Bids
+ for i := 0; i < len(spec.BidResponses); i++ {
+ assertMakeBidsOutput(t, filename, bidResponses[i], spec.BidResponses[i].Bids)
+ }
+}
diff --git a/adapters/adf/adf.go b/adapters/adf/adf.go
new file mode 100644
index 00000000000..f73e23aa07d
--- /dev/null
+++ b/adapters/adf/adf.go
@@ -0,0 +1,129 @@
+package adf
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+ endpoint string
+}
+
+// Builder builds a new instance of the Adf adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+ bidder := &adapter{
+ endpoint: config.Endpoint,
+ }
+ return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ var errors []error
+ var validImps = make([]openrtb2.Imp, 0, len(request.Imp))
+
+ for _, imp := range request.Imp {
+ var bidderExt adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ errors = append(errors, &errortypes.BadInput{
+ Message: err.Error(),
+ })
+ continue
+ }
+
+ var adfImpExt openrtb_ext.ExtImpAdf
+ if err := json.Unmarshal(bidderExt.Bidder, &adfImpExt); err != nil {
+ errors = append(errors, &errortypes.BadInput{
+ Message: err.Error(),
+ })
+ continue
+ }
+
+ imp.TagID = adfImpExt.MasterTagID.String()
+ validImps = append(validImps, imp)
+ }
+
+ request.Imp = validImps
+
+ requestJSON, err := json.Marshal(request)
+ if err != nil {
+ errors = append(errors, err)
+ return nil, errors
+ }
+
+ requestData := &adapters.RequestData{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: requestJSON,
+ }
+
+ return []*adapters.RequestData{requestData}, errors
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if responseData.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if responseData.StatusCode == http.StatusBadRequest {
+ err := &errortypes.BadInput{
+ Message: "Unexpected status code: 400. Bad request from publisher.",
+ }
+ return nil, []error{err}
+ }
+
+ if responseData.StatusCode != http.StatusOK {
+ err := &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unexpected status code: %d.", responseData.StatusCode),
+ }
+ return nil, []error{err}
+ }
+
+ var response openrtb2.BidResponse
+ if err := json.Unmarshal(responseData.Body, &response); err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+ bidResponse.Currency = response.Cur
+ var errors []error
+ for _, seatBid := range response.SeatBid {
+ for i, bid := range seatBid.Bid {
+ bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &seatBid.Bid[i],
+ BidType: bidType,
+ })
+ }
+ }
+
+ return bidResponse, errors
+}
+
+func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
+ for _, imp := range imps {
+ if imp.ID == impID {
+ if imp.Banner != nil {
+ return openrtb_ext.BidTypeBanner, nil
+ } else if imp.Video != nil {
+ return openrtb_ext.BidTypeVideo, nil
+ } else if imp.Native != nil {
+ return openrtb_ext.BidTypeNative, nil
+ }
+ }
+ }
+
+ return "", &errortypes.BadInput{
+ Message: fmt.Sprintf("Failed to find supported impression \"%s\" mediatype", impID),
+ }
+}
diff --git a/adapters/adf/adf_test.go b/adapters/adf/adf_test.go
new file mode 100644
index 00000000000..ab348db36ae
--- /dev/null
+++ b/adapters/adf/adf_test.go
@@ -0,0 +1,20 @@
+package adf
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderAdf, config.Adapter{
+ Endpoint: "https://adx.adform.net/adx/openrtb"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "adftest", bidder)
+}
diff --git a/adapters/adf/adftest/exemplary/multi-format.json b/adapters/adf/adftest/exemplary/multi-format.json
new file mode 100644
index 00000000000..6b917658cdc
--- /dev/null
+++ b/adapters/adf/adftest/exemplary/multi-format.json
@@ -0,0 +1,118 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id-1",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "placement": 1
+ }
+ }, {
+ "id": "test-imp-id-2",
+ "ext": {
+ "bidder": {
+ "mid": "828783"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ }
+ }]
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id-1",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "placement": 1
+ },
+ "tagid": "828782"
+ }, {
+ "id": "test-imp-id-2",
+ "ext": {
+ "bidder": {
+ "mid": "828783"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "tagid": "828783"
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "bid": [{
+ "id": "test-bid-id-1",
+ "impid": "test-imp-id-1",
+ "price": 10,
+ "adm": "{video xml}",
+ "adomain": [],
+ "crid": "test-creative-id-1"
+ }]
+ }, {
+ "bid": [{
+ "id": "test-bid-id-2",
+ "impid": "test-imp-id-2",
+ "price": 2,
+ "adm": "{banner html}",
+ "adomain": [ "ad-domain" ],
+ "crid": "test-creative-id-2"
+ }]
+ }],
+ "cur": "EUR"
+ }
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "test-bid-id-1",
+ "impid": "test-imp-id-1",
+ "price": 10,
+ "adm": "{video xml}",
+ "crid": "test-creative-id-1"
+ },
+ "type": "video"
+ }, {
+ "bid": {
+ "id": "test-bid-id-2",
+ "impid": "test-imp-id-2",
+ "price": 2,
+ "adm": "{banner html}",
+ "adomain": [ "ad-domain" ],
+ "crid": "test-creative-id-2"
+ },
+ "type": "banner"
+ }]
+ }]
+}
diff --git a/adapters/adf/adftest/exemplary/multi-native.json b/adapters/adf/adftest/exemplary/multi-native.json
new file mode 100644
index 00000000000..40bd7e15773
--- /dev/null
+++ b/adapters/adf/adftest/exemplary/multi-native.json
@@ -0,0 +1,108 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id-1",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "native": {
+ "request": "{json string 1}",
+ "ver": "1.2"
+ }
+ }, {
+ "id": "test-imp-id-2",
+ "ext": {
+ "bidder": {
+ "mid": "828783"
+ }
+ },
+ "native": {
+ "request": "{json string 2}",
+ "ver": "1.2"
+ }
+ }]
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id-1",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "native": {
+ "request": "{json string 1}",
+ "ver": "1.2"
+ },
+ "tagid": "828782"
+ }, {
+ "id": "test-imp-id-2",
+ "ext": {
+ "bidder": {
+ "mid": "828783"
+ }
+ },
+ "native": {
+ "request": "{json string 2}",
+ "ver": "1.2"
+ },
+ "tagid": "828783"
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "bid": [{
+ "id": "test-bid-id-1",
+ "impid": "test-imp-id-1",
+ "price": 10,
+ "adm": "{json response string 1}",
+ "adomain": [],
+ "crid": "test-creative-id-1"
+ }, {
+ "id": "test-bid-id-2",
+ "impid": "test-imp-id-2",
+ "price": 2,
+ "adm": "{json response string 2}",
+ "adomain": [ "ad-domain" ],
+ "crid": "test-creative-id-2"
+ }]
+ }],
+ "cur": "EUR"
+ }
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "test-bid-id-1",
+ "impid": "test-imp-id-1",
+ "price": 10,
+ "adm": "{json response string 1}",
+ "crid": "test-creative-id-1"
+ },
+ "type": "native"
+ }, {
+ "bid": {
+ "id": "test-bid-id-2",
+ "impid": "test-imp-id-2",
+ "price": 2,
+ "adm": "{json response string 2}",
+ "adomain": [ "ad-domain" ],
+ "crid": "test-creative-id-2"
+ },
+ "type": "native"
+ }]
+ }]
+}
diff --git a/adapters/adf/adftest/exemplary/single-banner.json b/adapters/adf/adftest/exemplary/single-banner.json
new file mode 100644
index 00000000000..5fdfe8af7c8
--- /dev/null
+++ b/adapters/adf/adftest/exemplary/single-banner.json
@@ -0,0 +1,95 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ }
+ }],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "some-page-url"
+ },
+ "device": {
+ "w": 1920,
+ "h": 800
+ }
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "tagid": "828782"
+ }],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "some-page-url"
+ },
+ "device": {
+ "w": 1920,
+ "h": 800
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "bid": [{
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 10,
+ "adm": "{banner html}",
+ "adomain": [ "test.com" ],
+ "crid": "test-creative-id"
+ }]
+ }],
+ "cur": "USD"
+ }
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 10,
+ "adm": "{banner html}",
+ "crid": "test-creative-id",
+ "adomain": [ "test.com" ]
+ },
+ "type": "banner"
+ }
+ ]
+ }]
+}
diff --git a/adapters/adf/adftest/exemplary/single-native.json b/adapters/adf/adftest/exemplary/single-native.json
new file mode 100644
index 00000000000..909f8cec9a7
--- /dev/null
+++ b/adapters/adf/adftest/exemplary/single-native.json
@@ -0,0 +1,90 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "native": {
+ "request": "{json string}",
+ "ver": "1.2"
+ }
+ }],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "some-page-url"
+ },
+ "device": {
+ "w": 1920,
+ "h": 800
+ }
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "native": {
+ "request": "{json string}",
+ "ver": "1.2"
+ },
+ "tagid": "828782"
+ }],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "some-page-url"
+ },
+ "device": {
+ "w": 1920,
+ "h": 800
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "bid": [{
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 10,
+ "adm": "{json response string}",
+ "adomain": [],
+ "crid": "test-creative-id"
+ }]
+ }],
+ "cur": "USD"
+ }
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 10,
+ "adm": "{json response string}",
+ "crid": "test-creative-id"
+ },
+ "type": "native"
+ }
+ ]
+ }]
+}
diff --git a/adapters/adf/adftest/exemplary/single-video.json b/adapters/adf/adftest/exemplary/single-video.json
new file mode 100644
index 00000000000..ebe4c6f1b38
--- /dev/null
+++ b/adapters/adf/adftest/exemplary/single-video.json
@@ -0,0 +1,93 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "placement": 1
+ }
+ }],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "some-page-url"
+ },
+ "device": {
+ "w": 1920,
+ "h": 800
+ }
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "ext": {
+ "bidder": {
+ "mid": "828782"
+ }
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "placement": 1
+ },
+ "tagid": "828782"
+ }],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "some-page-url"
+ },
+ "device": {
+ "w": 1920,
+ "h": 800
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "bid": [{
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 10,
+ "adm": "{vast xml}",
+ "crid": "test-creative-id"
+ }]
+ }],
+ "cur": "USD"
+ }
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 10,
+ "adm": "{vast xml}",
+ "crid": "test-creative-id"
+ },
+ "type": "video"
+ }
+ ]
+ }]
+}
diff --git a/adapters/adf/adftest/params/race/native.json b/adapters/adf/adftest/params/race/native.json
new file mode 100644
index 00000000000..79535d85da4
--- /dev/null
+++ b/adapters/adf/adftest/params/race/native.json
@@ -0,0 +1,3 @@
+{
+ "mid": "828782"
+}
diff --git a/adapters/adf/adftest/supplemental/bad-request.json b/adapters/adf/adftest/supplemental/bad-request.json
new file mode 100644
index 00000000000..7424eae4656
--- /dev/null
+++ b/adapters/adf/adftest/supplemental/bad-request.json
@@ -0,0 +1,48 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ }
+ }]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ },
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "tagid": "12345"
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Bad request from publisher.",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adf/adftest/supplemental/empty-response.json b/adapters/adf/adftest/supplemental/empty-response.json
new file mode 100644
index 00000000000..b2d6eab97fe
--- /dev/null
+++ b/adapters/adf/adftest/supplemental/empty-response.json
@@ -0,0 +1,42 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ }
+ }]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ },
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "tagid": "12345"
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json b/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json
new file mode 100644
index 00000000000..2f01e7eaae9
--- /dev/null
+++ b/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json
@@ -0,0 +1,59 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "audio": {},
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ }
+ }]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ },
+ "id": "test-imp-id",
+ "audio": {
+ "mimes": null
+ },
+ "tagid": "12345"
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "bid": [{
+ "id": "test-bid-id",
+ "impid": "test-imp-id",
+ "price": 10,
+ "adm": "{vast xml}",
+ "crid": "test-creative-id"
+ }]
+ }],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Failed to find supported impression \"test-imp-id\" mediatype",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adf/adftest/supplemental/nobid-response.json b/adapters/adf/adftest/supplemental/nobid-response.json
new file mode 100644
index 00000000000..ec559aa7468
--- /dev/null
+++ b/adapters/adf/adftest/supplemental/nobid-response.json
@@ -0,0 +1,49 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ }
+ }]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ },
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "tagid": "12345"
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": null,
+ "bidid": null,
+ "cur": null
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": []
+}
diff --git a/adapters/adf/adftest/supplemental/server-error.json b/adapters/adf/adftest/supplemental/server-error.json
new file mode 100644
index 00000000000..15604ad2189
--- /dev/null
+++ b/adapters/adf/adftest/supplemental/server-error.json
@@ -0,0 +1,49 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ }
+ }]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ },
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "tagid": "12345"
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 500,
+ "body": "Server error"
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 500.",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adf/adftest/supplemental/unparsable-response.json b/adapters/adf/adftest/supplemental/unparsable-response.json
new file mode 100644
index 00000000000..091f05cea22
--- /dev/null
+++ b/adapters/adf/adftest/supplemental/unparsable-response.json
@@ -0,0 +1,49 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ }
+ }]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://adx.adform.net/adx/openrtb",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "ext": {
+ "bidder": {
+ "mid": 12345
+ }
+ },
+ "id": "test-imp-id",
+ "native": {
+ "request": ""
+ },
+ "tagid": "12345"
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": ""
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/adf/params_test.go b/adapters/adf/params_test.go
new file mode 100644
index 00000000000..779d3fb6f2d
--- /dev/null
+++ b/adapters/adf/params_test.go
@@ -0,0 +1,57 @@
+package adf
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/adf.json
+//
+// These also validate the format of the external API: request.imp[i].ext.adf
+
+// TestValidParams makes sure that the adform 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.BidderAdf, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected adform params: %s", validParam)
+ }
+ }
+}
+
+// TestInvalidParams makes sure that the adform 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.BidderAdf, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"mid":123}`,
+ `{"mid":"123"}`,
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `4.2`,
+ `[]`,
+ `{}`,
+ `{"notmid":"123"}`,
+ `{"mid":"placementID"}`,
+}
diff --git a/adapters/adf/usersync.go b/adapters/adf/usersync.go
new file mode 100644
index 00000000000..e3bd11422c4
--- /dev/null
+++ b/adapters/adf/usersync.go
@@ -0,0 +1,12 @@
+package adf
+
+import (
+ "text/template"
+
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/usersync"
+)
+
+func NewAdfSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("adf", temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/adf/usersync_test.go b/adapters/adf/usersync_test.go
new file mode 100644
index 00000000000..693e6418444
--- /dev/null
+++ b/adapters/adf/usersync_test.go
@@ -0,0 +1,31 @@
+package adf
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/prebid/prebid-server/privacy"
+ "github.com/prebid/prebid-server/privacy/gdpr"
+)
+
+func TestAdfSyncer(t *testing.T) {
+ syncURL := "https://cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewAdfSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "A",
+ Consent: "B",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "https://cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadf%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24UID", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/adform/adformtest/supplemental/user-nil.json b/adapters/adform/adformtest/supplemental/user-nil.json
index 5f02fe85971..96ea1dbff71 100644
--- a/adapters/adform/adformtest/supplemental/user-nil.json
+++ b/adapters/adform/adformtest/supplemental/user-nil.json
@@ -31,12 +31,7 @@
},
"user": {
"ext": {
- "consent": "abc2",
- "digitrust": {
- "ID": "digitrustId",
- "KeyV": 1,
- "Pref": 0
- }
+ "consent": "abc2"
}
}
},
diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go
index ec49950a17e..5008b0ce5c6 100644
--- a/adapters/admixer/admixer.go
+++ b/adapters/admixer/admixer.go
@@ -100,14 +100,17 @@ func preprocess(imp *openrtb2.Imp) error {
}
//don't use regexp due to possible performance reduce
- if len(admixerExt.ZoneId) != 36 {
+ if len(admixerExt.ZoneId) < 32 || len(admixerExt.ZoneId) > 36 {
return &errortypes.BadInput{
Message: "ZoneId must be UUID/GUID",
}
}
imp.TagID = admixerExt.ZoneId
- imp.BidFloor = admixerExt.CustomBidFloor
+
+ if imp.BidFloor == 0 && admixerExt.CustomBidFloor > 0 {
+ imp.BidFloor = admixerExt.CustomBidFloor
+ }
imp.Ext = nil
diff --git a/adapters/admixer/admixertest/exemplary/optional-params.json b/adapters/admixer/admixertest/exemplary/optional-params.json
index 8ef112bbdb5..b93aa9c8154 100644
--- a/adapters/admixer/admixertest/exemplary/optional-params.json
+++ b/adapters/admixer/admixertest/exemplary/optional-params.json
@@ -44,6 +44,76 @@
}
}
}
+ },
+ {
+ "id": "test-imp-id",
+ "bidfloor": 0.5,
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2",
+ "customParams": {
+ "foo": [
+ "bar",
+ "baz"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "id": "test-imp-id",
+ "bidfloor": 0.5,
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2",
+ "customParams": {
+ "foo": [
+ "bar",
+ "baz"
+ ]
+ }
+ },
+ "customFloor": 0.9
+ }
+ },
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "customFloor": 0.9,
+ "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2",
+ "customParams": {
+ "foo": [
+ "bar",
+ "baz"
+ ]
+ }
+ }
+ }
}
]
},
@@ -92,6 +162,69 @@
]
}
}
+ },
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2",
+ "bidfloor": 0.5,
+ "ext": {
+ "customParams": {
+ "foo": [
+ "bar",
+ "baz"
+ ]
+ }
+ }
+ },
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2",
+ "bidfloor": 0.5,
+ "ext": {
+ "customParams": {
+ "foo": [
+ "bar",
+ "baz"
+ ]
+ }
+ }
+ },
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2",
+ "bidfloor": 0.9,
+ "ext": {
+ "customParams": {
+ "foo": [
+ "bar",
+ "baz"
+ ]
+ }
+ }
}
]
}
diff --git a/adapters/admixer/params_test.go b/adapters/admixer/params_test.go
index 71cccb6a3da..11f3feb0657 100644
--- a/adapters/admixer/params_test.go
+++ b/adapters/admixer/params_test.go
@@ -44,6 +44,8 @@ var validParams = []string{
`{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customFloor": 0.1}`,
`{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customParams": {"foo": "bar"}}`,
`{"zone": "9ff668a2-4122-462e-aaf8-36ea3a54ba21", "customFloor": 0.1, "customParams": {"foo": ["bar", "baz"]}}`,
+ `{"zone": "9FF668A24122462EAAF836EA3A54BA21"}`,
+ `{"zone": "9FF668A24122462EAAF836EA3A54BA212"}`,
}
var invalidParams = []string{
@@ -54,4 +56,6 @@ var invalidParams = []string{
`{"zone": "123", "customFloor": "0.1"}`,
`{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customFloor": -0.1}`,
`{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customParams": "foo: bar"}`,
+ `{"zone": "9FF668A24122462EAAF836EA3A54BA2"}`,
+ `{"zone": "9FF668A24122462EAAF836EA3A54BA2112336"}`,
}
diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go
index 635cba8c9bc..6a0eb892be4 100644
--- a/adapters/adocean/adocean.go
+++ b/adapters/adocean/adocean.go
@@ -21,7 +21,7 @@ import (
"github.com/prebid/prebid-server/openrtb_ext"
)
-const adapterVersion = "1.1.0"
+const adapterVersion = "1.2.0"
const maxUriLength = 8000
const measurementCode = `
",
+ "adomain": ["test.com"],
+ "cat": ["IAB24"],
+ "cid": "1IP31",
+ "crid": "1KS13",
+ "w": 0,
+ "h": 0
+ }],
+ "seat": "1"
+ }],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "6ca51ef38eb42820d27503ee96b634b5",
+ "impid": "126365",
+ "price": 0.1,
+ "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}",
+ "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}",
+ "adm": "
\" width=\"300\" height=\"250\" scrolling=\"no\" style=\"background-color:lightgray;border:0;overflow:hidden\">",
+ "adomain": ["test.com"],
+ "cat": ["IAB24"],
+ "cid": "1IP31",
+ "crid": "1KS13"
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/exemplary/banner.json b/adapters/e_volution/evolutiontest/exemplary/banner.json
new file mode 100644
index 00000000000..68fda4907e2
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/exemplary/banner.json
@@ -0,0 +1,174 @@
+{
+ "mockBidRequest": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "secure": 1,
+ "bidfloor": 0.20,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250,
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "key": "test_banner"
+ }
+ }
+ }],
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City",
+ "lat": 19.42,
+ "lon": -99.1663
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://service.e-volution.ai/pbserver",
+ "body": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }],
+ "w": 300,
+ "h": 250
+ },
+ "bidfloor": 0.2,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "key": "test_banner"
+ }
+ }
+ }],
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "lat": 19.42,
+ "lon": -99.1663,
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City"
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "bidid": "880184da7ec02b1f42802acb46b63b3c",
+ "seatbid": [{
+ "bid": [{
+ "id": "6ca51ef38eb42820d27503ee96b634b5",
+ "impid": "126365",
+ "price": 0.1,
+ "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}",
+ "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}",
+ "adm": "",
+ "adomain": ["test.com"],
+ "cat": ["IAB24"],
+ "cid": "1IP31",
+ "crid": "1KS13",
+ "w": 0,
+ "h": 0,
+ "ext": {
+ "mediaType": "banner"
+ }
+ }],
+ "seat": "1"
+ }],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "6ca51ef38eb42820d27503ee96b634b5",
+ "impid": "126365",
+ "price": 0.1,
+ "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}",
+ "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}",
+ "adm": "",
+ "adomain": ["test.com"],
+ "cat": ["IAB24"],
+ "cid": "1IP31",
+ "crid": "1KS13",
+ "ext": {
+ "mediaType": "banner"
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/exemplary/native.json b/adapters/e_volution/evolutiontest/exemplary/native.json
new file mode 100644
index 00000000000..724f55f6b8b
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/exemplary/native.json
@@ -0,0 +1,181 @@
+{
+ "mockBidRequest": {
+ "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+ "tmax": 500,
+ "at": 1,
+ "device": {
+ "dnt": 0,
+ "devicetype": 2,
+ "js": 1,
+ "ip": "79.26.58.249",
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
+ "os": "Mac OS",
+ "language": "en",
+ "geo": {
+ "country": "ITA"
+ }
+ },
+ "user": {
+ "id": "l1eHS1lZE41a",
+ "ext": {
+ "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA"
+ }
+ },
+ "site": {
+ "id": "57078628",
+ "domain": "www.affaritaliani.it",
+ "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html",
+ "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce",
+ "name": "www.affaritaliani.it",
+ "publisher": {
+ "id": "439696",
+ "name": "www.affaritaliani.it"
+ },
+ "cat": [
+ "IAB12"
+ ]
+ },
+ "publisher": {},
+ "cur": [
+ "USD"
+ ],
+ "bcat": [
+ "IAB12-2",
+ "IAB9-7"
+ ],
+ "imp": [{
+ "id": "1",
+ "bidfloor": 0.1,
+ "bidfloorcur": "USD",
+ "instl": 0,
+ "secure": 1,
+ "native": {
+ "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "key": "test_native"
+ }
+ }
+ }],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ }
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "http://service.e-volution.ai/pbserver",
+ "body": {
+ "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+ "imp": [{
+ "id": "1",
+ "native": {
+ "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+ "ver": "1.1"
+ },
+ "bidfloor": 0.1,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "key": "test_native"
+ }
+ }
+ }],
+ "site": {
+ "id": "57078628",
+ "name": "www.affaritaliani.it",
+ "domain": "www.affaritaliani.it",
+ "cat": ["IAB12"],
+ "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html",
+ "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce",
+ "publisher": {
+ "id": "439696",
+ "name": "www.affaritaliani.it"
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
+ "geo": {
+ "country": "ITA"
+ },
+ "dnt": 0,
+ "ip": "79.26.58.249",
+ "devicetype": 2,
+ "os": "Mac OS",
+ "js": 1,
+ "language": "en"
+ },
+ "user": {
+ "id": "l1eHS1lZE41a",
+ "ext": {
+ "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA"
+ }
+ },
+ "at": 1,
+ "tmax": 500,
+ "cur": ["USD"],
+ "bcat": ["IAB12-2", "IAB9-7"],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+ "bidid": "359da97d0384d8a14767029c18fd840d",
+ "seatbid": [{
+ "bid": [{
+ "id": "1af875cae46410c18e4d8b1fcc909e6c",
+ "impid": "1",
+ "price": 0.1,
+ "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}",
+ "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}",
+ "adm": "{\"link\":{\"url\":\"https://e-volution.ai\"},\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"text\":\"Viralize test\"}},{\"id\":2,\"required\":1,\"img\":{}},{\"id\":4,\"data\":{\"value\":\"3$\"}},{\"id\":5,\"data\":{\"value\":\"2$\"}},{\"id\":6,\"data\":{\"value\":\"viralize\"}}],\"ver\":\"1.1\",\"imptrackers\":[\"https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=359da97d0384d8a14767029c18fd840d&cp=${AUCTION_PRICE}\",\"https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=1&gdpr_consent=CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA&ccpa_consent=\"]}",
+ "adomain": ["test.com"],
+ "cat": ["IAB24"],
+ "cid": "1IP31",
+ "crid": "1KS13",
+ "w": 0,
+ "h": 0,
+ "ext": {
+ "mediaType": "native"
+ }
+ }],
+ "seat": "1"
+ }],
+ "cur": "USD"
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "bids": [{
+ "bid": {
+ "id": "1af875cae46410c18e4d8b1fcc909e6c",
+ "impid": "1",
+ "price": 0.1,
+ "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}",
+ "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}",
+ "adm": "{\"link\":{\"url\":\"https://e-volution.ai\"},\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"text\":\"Viralize test\"}},{\"id\":2,\"required\":1,\"img\":{}},{\"id\":4,\"data\":{\"value\":\"3$\"}},{\"id\":5,\"data\":{\"value\":\"2$\"}},{\"id\":6,\"data\":{\"value\":\"viralize\"}}],\"ver\":\"1.1\",\"imptrackers\":[\"https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=359da97d0384d8a14767029c18fd840d&cp=${AUCTION_PRICE}\",\"https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=1&gdpr_consent=CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA&ccpa_consent=\"]}",
+ "adomain": ["test.com"],
+ "cat": ["IAB24"],
+ "cid": "1IP31",
+ "crid": "1KS13",
+ "ext": {
+ "mediaType": "native"
+ }
+ },
+ "type": "native"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/exemplary/video.json b/adapters/e_volution/evolutiontest/exemplary/video.json
new file mode 100644
index 00000000000..f7a03146918
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/exemplary/video.json
@@ -0,0 +1,200 @@
+{
+ "mockBidRequest": {
+ "id": "6d773e60ab245243a04217d5f7e9e786",
+ "imp": [{
+ "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+ "video": {
+ "mimes": ["video/mp4", "video/ogg", "video/webm"],
+ "minduration": 3,
+ "maxduration": 3000,
+ "protocols": [2, 3, 5, 6, 7, 8],
+ "w": 480,
+ "h": 320,
+ "linearity": 1,
+ "playbackmethod": [2],
+ "pos": 0
+ },
+ "bidfloor": 0.40404,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "exp": 3600,
+ "ext": {
+ "bidder": {
+ "key": "test_video"
+ }
+ }
+ }],
+ "site": {
+ "id": "1c94d85ea1a55ebf3325a82935240614",
+ "domain": "kiwilimon.com",
+ "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"],
+ "page": "https://kiwilimon.com/",
+ "publisher": {
+ "id": "866d6b3a4a483ed4a454a1acaa588852"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+ "geo": {
+ "lat": 20.6668,
+ "lon": -103.3918,
+ "accuracy": 5,
+ "country": "MEX",
+ "region": "JAL",
+ "zip": "45186"
+ },
+ "dnt": 0,
+ "lmt": 0,
+ "ip": "187.152.119.145",
+ "devicetype": 2,
+ "os": "Windows",
+ "osv": "10",
+ "carrier": "Telmex"
+ },
+ "test": 1,
+ "at": 1,
+ "tmax": 500,
+ "cur": ["USD"],
+ "source": {
+ "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://service.e-volution.ai/pbserver",
+ "body": {
+ "id": "6d773e60ab245243a04217d5f7e9e786",
+ "imp": [{
+ "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+ "video": {
+ "mimes": ["video/mp4", "video/ogg", "video/webm"],
+ "minduration": 3,
+ "maxduration": 3000,
+ "protocols": [2, 3, 5, 6, 7, 8],
+ "w": 480,
+ "h": 320,
+ "linearity": 1,
+ "playbackmethod": [2],
+ "pos": 0
+ },
+ "bidfloor": 0.40404,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "exp": 3600,
+ "ext": {
+ "bidder": {
+ "key": "test_video"
+ }
+ }
+ }],
+ "site": {
+ "id": "1c94d85ea1a55ebf3325a82935240614",
+ "domain": "kiwilimon.com",
+ "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"],
+ "page": "https://kiwilimon.com/",
+ "publisher": {
+ "id": "866d6b3a4a483ed4a454a1acaa588852"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+ "geo": {
+ "lat": 20.6668,
+ "lon": -103.3918,
+ "accuracy": 5,
+ "country": "MEX",
+ "region": "JAL",
+ "zip": "45186"
+ },
+ "dnt": 0,
+ "lmt": 0,
+ "ip": "187.152.119.145",
+ "devicetype": 2,
+ "os": "Windows",
+ "osv": "10",
+ "carrier": "Telmex"
+ },
+ "test": 1,
+ "at": 1,
+ "tmax": 500,
+ "cur": ["USD"],
+ "source": {
+ "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "6d773e60ab245243a04217d5f7e9e786",
+ "bidid": "ccbd63285c0e7b69602d90319bda6be4",
+ "seatbid": [{
+ "bid": [{
+ "id": "7f6c6d6ba432059a5305815a8d740283",
+ "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+ "price": 0.1,
+ "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+ "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+ "adm": "846500:00:16",
+ "adomain": ["test.com"],
+ "cat": ["IAB24"],
+ "cid": "1IP31",
+ "crid": "1KS13",
+ "w": 0,
+ "h": 0,
+ "ext": {
+ "mediaType": "video"
+ }
+ }],
+ "seat": "1"
+ }],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7f6c6d6ba432059a5305815a8d740283",
+ "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+ "price": 0.1,
+ "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+ "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}",
+ "adm": "846500:00:16",
+ "adomain": ["test.com"],
+ "cat": ["IAB24"],
+ "cid": "1IP31",
+ "crid": "1KS13",
+ "ext": {
+ "mediaType": "video"
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/params/race/banner.json b/adapters/e_volution/evolutiontest/params/race/banner.json
new file mode 100644
index 00000000000..0a04c95b072
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+ "key": "test_banner"
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/params/race/native.json b/adapters/e_volution/evolutiontest/params/race/native.json
new file mode 100644
index 00000000000..032b9dd56d8
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/params/race/native.json
@@ -0,0 +1,3 @@
+{
+ "key": "test_native"
+}
diff --git a/adapters/e_volution/evolutiontest/params/race/video.json b/adapters/e_volution/evolutiontest/params/race/video.json
new file mode 100644
index 00000000000..87071003920
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+ "key": "test_video"
+}
diff --git a/adapters/e_volution/evolutiontest/supplemental/bad-response.json b/adapters/e_volution/evolutiontest/supplemental/bad-response.json
new file mode 100644
index 00000000000..75f3cb455af
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/supplemental/bad-response.json
@@ -0,0 +1,158 @@
+{
+ "mockBidRequest": {
+ "id": "6d773e60ab245243a04217d5f7e9e786",
+ "imp": [{
+ "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+ "video": {
+ "mimes": ["video/mp4", "video/ogg", "video/webm"],
+ "minduration": 3,
+ "maxduration": 3000,
+ "protocols": [2, 3, 5, 6, 7, 8],
+ "w": 480,
+ "h": 320,
+ "linearity": 1,
+ "playbackmethod": [2],
+ "pos": 0
+ },
+ "bidfloor": 0.40404,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "exp": 3600,
+ "ext": {
+ "bidder": {
+ "key": "test_video"
+ }
+ }
+ }],
+ "site": {
+ "id": "1c94d85ea1a55ebf3325a82935240614",
+ "domain": "kiwilimon.com",
+ "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"],
+ "page": "https://kiwilimon.com/",
+ "publisher": {
+ "id": "866d6b3a4a483ed4a454a1acaa588852"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+ "geo": {
+ "lat": 20.6668,
+ "lon": -103.3918,
+ "accuracy": 5,
+ "country": "MEX",
+ "region": "JAL",
+ "zip": "45186"
+ },
+ "dnt": 0,
+ "lmt": 0,
+ "ip": "187.152.119.145",
+ "devicetype": 2,
+ "os": "Windows",
+ "osv": "10",
+ "carrier": "Telmex"
+ },
+ "test": 1,
+ "at": 1,
+ "tmax": 500,
+ "cur": ["USD"],
+ "source": {
+ "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://service.e-volution.ai/pbserver",
+ "body": {
+ "id": "6d773e60ab245243a04217d5f7e9e786",
+ "imp": [{
+ "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1",
+ "video": {
+ "mimes": ["video/mp4", "video/ogg", "video/webm"],
+ "minduration": 3,
+ "maxduration": 3000,
+ "protocols": [2, 3, 5, 6, 7, 8],
+ "w": 480,
+ "h": 320,
+ "linearity": 1,
+ "playbackmethod": [2],
+ "pos": 0
+ },
+ "bidfloor": 0.40404,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "exp": 3600,
+ "ext": {
+ "bidder": {
+ "key": "test_video"
+ }
+ }
+ }],
+ "site": {
+ "id": "1c94d85ea1a55ebf3325a82935240614",
+ "domain": "kiwilimon.com",
+ "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"],
+ "page": "https://kiwilimon.com/",
+ "publisher": {
+ "id": "866d6b3a4a483ed4a454a1acaa588852"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36",
+ "geo": {
+ "lat": 20.6668,
+ "lon": -103.3918,
+ "accuracy": 5,
+ "country": "MEX",
+ "region": "JAL",
+ "zip": "45186"
+ },
+ "dnt": 0,
+ "lmt": 0,
+ "ip": "187.152.119.145",
+ "devicetype": 2,
+ "os": "Windows",
+ "osv": "10",
+ "carrier": "Telmex"
+ },
+ "test": 1,
+ "at": 1,
+ "tmax": 500,
+ "cur": ["USD"],
+ "source": {
+ "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": ""
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Bad response, json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json b/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json
new file mode 100644
index 00000000000..c9a103aea39
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json
@@ -0,0 +1,149 @@
+{
+ "mockBidRequest": {
+ "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+ "tmax": 500,
+ "at": 1,
+ "device": {
+ "dnt": 0,
+ "devicetype": 2,
+ "js": 1,
+ "ip": "79.26.58.249",
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
+ "os": "Mac OS",
+ "language": "en",
+ "geo": {
+ "country": "ITA"
+ }
+ },
+ "user": {
+ "id": "l1eHS1lZE41a",
+ "ext": {
+ "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA"
+ }
+ },
+ "site": {
+ "id": "57078628",
+ "domain": "www.affaritaliani.it",
+ "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html",
+ "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce",
+ "name": "www.affaritaliani.it",
+ "publisher": {
+ "id": "439696",
+ "name": "www.affaritaliani.it"
+ },
+ "cat": [
+ "IAB12"
+ ]
+ },
+ "publisher": {},
+ "cur": [
+ "USD"
+ ],
+ "bcat": [
+ "IAB12-2",
+ "IAB9-7"
+ ],
+ "imp": [{
+ "id": "1",
+ "bidfloor": 0.1,
+ "bidfloorcur": "USD",
+ "instl": 0,
+ "secure": 1,
+ "native": {
+ "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "key": "test_native"
+ }
+ }
+ }],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ }
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "http://service.e-volution.ai/pbserver",
+ "body": {
+ "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+ "imp": [{
+ "id": "1",
+ "native": {
+ "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+ "ver": "1.1"
+ },
+ "bidfloor": 0.1,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "key": "test_native"
+ }
+ }
+ }],
+ "site": {
+ "id": "57078628",
+ "name": "www.affaritaliani.it",
+ "domain": "www.affaritaliani.it",
+ "cat": ["IAB12"],
+ "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html",
+ "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce",
+ "publisher": {
+ "id": "439696",
+ "name": "www.affaritaliani.it"
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
+ "geo": {
+ "country": "ITA"
+ },
+ "dnt": 0,
+ "ip": "79.26.58.249",
+ "devicetype": 2,
+ "os": "Mac OS",
+ "js": 1,
+ "language": "en"
+ },
+ "user": {
+ "id": "l1eHS1lZE41a",
+ "ext": {
+ "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA"
+ }
+ },
+ "at": 1,
+ "tmax": 500,
+ "cur": ["USD"],
+ "bcat": ["IAB12-2", "IAB9-7"],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+ "bidid": "359da97d0384d8a14767029c18fd840d",
+ "seatbid": [],
+ "cur": "USD"
+ }
+ }
+ }],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Empty seatbid",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/supplemental/status-204.json b/adapters/e_volution/evolutiontest/supplemental/status-204.json
new file mode 100644
index 00000000000..85e89873fd2
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/supplemental/status-204.json
@@ -0,0 +1,126 @@
+{
+ "mockBidRequest": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "secure": 1,
+ "bidfloor": 0.20,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250,
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "key": "test_banner"
+ }
+ }
+ }],
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City",
+ "lat": 19.42,
+ "lon": -99.1663
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://service.e-volution.ai/pbserver",
+ "body": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }],
+ "w": 300,
+ "h": 250
+ },
+ "bidfloor": 0.2,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "key": "test_banner"
+ }
+ }
+ }],
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "lat": 19.42,
+ "lon": -99.1663,
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City"
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/supplemental/status-400.json b/adapters/e_volution/evolutiontest/supplemental/status-400.json
new file mode 100644
index 00000000000..b26e827200e
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/supplemental/status-400.json
@@ -0,0 +1,133 @@
+{
+ "mockBidRequest": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "secure": 1,
+ "bidfloor": 0.20,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250,
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "key": "45352"
+ }
+ }
+ }],
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City",
+ "lat": 19.42,
+ "lon": -99.1663
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://service.e-volution.ai/pbserver",
+ "body": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }],
+ "w": 300,
+ "h": 250
+ },
+ "bidfloor": 0.2,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "key": "45352"
+ }
+ }
+ }],
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "lat": 19.42,
+ "lon": -99.1663,
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City"
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 400,
+ "body": "The Key has a different ad format"
+ }
+ }
+ ],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Bad Request. \"The Key has a different ad format\"",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/supplemental/status-503.json b/adapters/e_volution/evolutiontest/supplemental/status-503.json
new file mode 100644
index 00000000000..0f289ea8d3e
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/supplemental/status-503.json
@@ -0,0 +1,125 @@
+{
+ "mockBidRequest": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "secure": 1,
+ "bidfloor": 0.20,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250,
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "key": "45352"
+ }
+ }
+ }],
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City",
+ "lat": 19.42,
+ "lon": -99.1663
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://service.e-volution.ai/pbserver",
+ "body": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }],
+ "w": 300,
+ "h": 250
+ },
+ "bidfloor": 0.2,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "key": "45352"
+ }
+ }
+ }],
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "lat": 19.42,
+ "lon": -99.1663,
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City"
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 503
+ }
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json b/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json
new file mode 100644
index 00000000000..5d0df32383e
--- /dev/null
+++ b/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json
@@ -0,0 +1,132 @@
+{
+ "mockBidRequest": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "secure": 1,
+ "bidfloor": 0.20,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250,
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "key": "45352"
+ }
+ }
+ }],
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City",
+ "lat": 19.42,
+ "lon": -99.1663
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://service.e-volution.ai/pbserver",
+ "body": {
+ "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365",
+ "imp": [{
+ "id": "126365",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }],
+ "w": 300,
+ "h": 250
+ },
+ "bidfloor": 0.2,
+ "bidfloorcur": "USD",
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "key": "45352"
+ }
+ }
+ }],
+ "site": {
+ "id": "12",
+ "domain": "milano.repubblica.it",
+ "cat": ["IAB12"],
+ "page": "https://milano.repubblica.it",
+ "publisher": {
+ "id": "45672"
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
+ "geo": {
+ "lat": 19.42,
+ "lon": -99.1663,
+ "country": "MEX",
+ "region": "CMX",
+ "city": "Mexico City"
+ },
+ "ip": "189.146.118.247",
+ "devicetype": 4,
+ "language": "it"
+ },
+ "user": {
+ "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5",
+ "ext": {
+ "consent": ""
+ }
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 401
+ }
+ }
+ ],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Something went wrong, please contact your Account Manager. Status Code: [ 401 ] ",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/e_volution/params_test.go b/adapters/e_volution/params_test.go
new file mode 100644
index 00000000000..2d3602fd72b
--- /dev/null
+++ b/adapters/e_volution/params_test.go
@@ -0,0 +1,49 @@
+package evolution
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+var validParams = []string{
+ `{ "key": "24" }`,
+}
+
+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.BidderEVolution, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected evolution params: %s", validParam)
+ }
+ }
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `4.2`,
+ `[]`,
+ `{}`,
+ `{ "anyparam": "anyvalue" }`,
+}
+
+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.BidderEVolution, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
diff --git a/adapters/e_volution/usersync.go b/adapters/e_volution/usersync.go
new file mode 100644
index 00000000000..f22784d018b
--- /dev/null
+++ b/adapters/e_volution/usersync.go
@@ -0,0 +1,12 @@
+package evolution
+
+import (
+ "text/template"
+
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/usersync"
+)
+
+func NewEvolutionSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("e_volution", temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/e_volution/usersync_test.go b/adapters/e_volution/usersync_test.go
new file mode 100644
index 00000000000..d7a3eba5f0a
--- /dev/null
+++ b/adapters/e_volution/usersync_test.go
@@ -0,0 +1,33 @@
+package evolution
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/prebid/prebid-server/privacy"
+ "github.com/prebid/prebid-server/privacy/ccpa"
+ "github.com/prebid/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNewEvolutionSyncer(t *testing.T) {
+ syncURL := "https://sync.test.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+ syncer := NewEvolutionSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "0",
+ Consent: "allGdpr",
+ },
+ CCPA: ccpa.Policy{
+ Consent: "1---",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "https://sync.test.com/pbserver?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go
index a23472e8892..63baa8a4ba5 100644
--- a/adapters/inmobi/inmobi.go
+++ b/adapters/inmobi/inmobi.go
@@ -124,6 +124,9 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
if imp.Video != nil {
mediaType = openrtb_ext.BidTypeVideo
}
+ if imp.Native != nil {
+ mediaType = openrtb_ext.BidTypeNative
+ }
break
}
}
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json
similarity index 100%
rename from adapters/inmobi/inmobitest/exemplary/simple-banner.json
rename to adapters/inmobi/inmobitest/exemplary/simple-app-banner.json
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-app-native.json b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json
new file mode 100644
index 00000000000..3a5bfd38412
--- /dev/null
+++ b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json
@@ -0,0 +1,105 @@
+{
+ "mockBidRequest": {
+ "app": {
+ "bundle": "com.example.app"
+ },
+ "id": "req-id",
+ "device": {
+ "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+ "ip": "1.1.1.1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+ },
+ "imp": [
+ {
+ "ext": {
+ "bidder": {
+ "plc": "1618933962959"
+ }
+ },
+ "native": {
+ "request": "{\"ver\":\"1.2\",\"context\":2,\"contextsubtype\":20,\"plcmttype\":11,\"plcmtcnt\":1,\"aurlsupport\":1,\"durlsupport\":1,\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"len\":140}},{\"id\":128,\"required\":0,\"img\":{\"wmin\":836,\"hmin\":627,\"type\":3}}]}"
+ },
+ "id": "imp-id"
+ }
+ ]
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+ "body": {
+ "app": {
+ "bundle": "com.example.app"
+ },
+ "id": "req-id",
+ "device": {
+ "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+ "ip": "1.1.1.1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+ },
+ "imp": [
+ {
+ "ext": {
+ "bidder": {
+ "plc": "1618933962959"
+ }
+ },
+ "native": {
+ "request": "{\"ver\":\"1.2\",\"context\":2,\"contextsubtype\":20,\"plcmttype\":11,\"plcmtcnt\":1,\"aurlsupport\":1,\"durlsupport\":1,\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"len\":140}},{\"id\":128,\"required\":0,\"img\":{\"wmin\":836,\"hmin\":627,\"type\":3}}]}"
+ },
+ "id": "imp-id"
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "req-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "ext": {
+ "prebid": {
+ "meta": {
+ "networkName": "inmobi"
+ }
+ }
+ },
+ "nurl": "https://some.event.url/params",
+ "crid": "123456789",
+ "adomain": [],
+ "price": 2.0,
+ "id": "1234",
+ "adm": "native-json",
+ "impid": "imp-id"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "bids": [{
+ "bid": {
+ "id": "1234",
+ "impid": "imp-id",
+ "price": 2.0,
+ "adm": "native-json",
+ "crid": "123456789",
+ "nurl": "https://some.event.url/params",
+ "ext": {
+ "prebid": {
+ "meta": {
+ "networkName": "inmobi"
+ }
+ }
+ }
+ },
+ "type": "native"
+ }]
+ }]
+}
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-video.json b/adapters/inmobi/inmobitest/exemplary/simple-app-video.json
similarity index 100%
rename from adapters/inmobi/inmobitest/exemplary/simple-video.json
rename to adapters/inmobi/inmobitest/exemplary/simple-app-video.json
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json
new file mode 100644
index 00000000000..131249ba8a1
--- /dev/null
+++ b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json
@@ -0,0 +1,105 @@
+{
+ "mockBidRequest": {
+ "site": {
+ "page": "https://www.inmobi.com"
+ },
+ "id": "req-id",
+ "device": {
+ "ip": "1.1.1.1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+ },
+ "imp": [
+ {
+ "ext": {
+ "bidder": {
+ "plc": "1617941157285"
+ }
+ },
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "id": "imp-id"
+ }
+ ]
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+ "body": {
+ "site": {
+ "page": "https://www.inmobi.com"
+ },
+ "id": "req-id",
+ "device": {
+ "ip": "1.1.1.1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+ },
+ "imp": [
+ {
+ "ext": {
+ "bidder": {
+ "plc": "1617941157285"
+ }
+ },
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "id": "imp-id"
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "req-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "ext": {
+ "prebid": {
+ "meta": {
+ "networkName": "inmobi"
+ }
+ }
+ },
+ "nurl": "https://some.event.url/params",
+ "crid": "123456789",
+ "adomain": [],
+ "price": 2.0,
+ "id": "1234",
+ "adm": "bannerhtml",
+ "impid": "imp-id"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "bids": [{
+ "bid": {
+ "id": "1234",
+ "impid": "imp-id",
+ "price": 2.0,
+ "adm": "bannerhtml",
+ "crid": "123456789",
+ "nurl": "https://some.event.url/params",
+ "ext": {
+ "prebid": {
+ "meta": {
+ "networkName": "inmobi"
+ }
+ }
+ }
+ },
+ "type": "banner"
+ }]
+ }]
+}
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-video.json b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json
new file mode 100644
index 00000000000..3aed605f416
--- /dev/null
+++ b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json
@@ -0,0 +1,109 @@
+{
+ "mockBidRequest": {
+ "site": {
+ "page": "https://www.inmobi.com"
+ },
+ "id": "req-id",
+ "device": {
+ "ip": "1.1.1.1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+ },
+ "imp": [
+ {
+ "ext": {
+ "bidder": {
+ "plc": "1621323101291"
+ }
+ },
+ "video": {
+ "w": 640,
+ "h": 360,
+ "mimes": ["video/mp4"]
+ },
+ "id": "imp-id"
+ }
+ ]
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+ "body": {
+ "site": {
+ "page": "https://www.inmobi.com"
+ },
+ "id": "req-id",
+ "device": {
+ "ip": "1.1.1.1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+ },
+ "imp": [
+ {
+ "ext": {
+ "bidder": {
+ "plc": "1621323101291"
+ }
+ },
+ "video": {
+ "w": 640,
+ "h": 360,
+ "mimes": ["video/mp4"]
+ },
+ "id": "imp-id"
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "req-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "ext": {
+ "prebid": {
+ "meta": {
+ "networkName": "inmobi"
+ }
+ }
+ },
+ "nurl": "https://some.event.url/params",
+ "crid": "123456789",
+ "adomain": [],
+ "price": 2.0,
+ "id": "1234",
+ "adm": " ",
+ "impid": "imp-id"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "bids": [{
+ "bid": {
+ "id": "1234",
+ "impid": "imp-id",
+ "price": 2.0,
+ "adm": " ",
+ "crid": "123456789",
+ "nurl": "https://some.event.url/params",
+ "ext": {
+ "prebid": {
+ "meta": {
+ "networkName": "inmobi"
+ }
+ }
+ }
+ },
+ "type": "video"
+ }]
+ }]
+}
+
+
diff --git a/adapters/inmobi/usersync.go b/adapters/inmobi/usersync.go
new file mode 100644
index 00000000000..7f022e3c5d0
--- /dev/null
+++ b/adapters/inmobi/usersync.go
@@ -0,0 +1,12 @@
+package inmobi
+
+import (
+ "text/template"
+
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/usersync"
+)
+
+func NewInmobiSyncer(template *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("inmobi", template, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/interactiveoffers/interactiveoffers.go b/adapters/interactiveoffers/interactiveoffers.go
new file mode 100644
index 00000000000..fd4cd5807f5
--- /dev/null
+++ b/adapters/interactiveoffers/interactiveoffers.go
@@ -0,0 +1,79 @@
+package interactiveoffers
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+ endpoint string
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ requestJSON, err := json.Marshal(request)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ requestData := &adapters.RequestData{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: requestJSON,
+ }
+
+ return []*adapters.RequestData{requestData}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if responseData.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if responseData.StatusCode == http.StatusBadRequest {
+ err := &errortypes.BadInput{
+ Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
+ }
+ return nil, []error{err}
+ }
+
+ if responseData.StatusCode != http.StatusOK {
+ err := &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
+ }
+ return nil, []error{err}
+ }
+
+ var response openrtb2.BidResponse
+ if err := json.Unmarshal(responseData.Body, &response); err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+ bidResponse.Currency = response.Cur
+ for _, seatBid := range response.SeatBid {
+ for _, bid := range seatBid.Bid {
+ bid := bid // pin https://github.com/kyoh86/scopelint#whats-this
+ b := &adapters.TypedBid{
+ Bid: &bid,
+ BidType: openrtb_ext.BidTypeBanner,
+ }
+ bidResponse.Bids = append(bidResponse.Bids, b)
+ }
+ }
+ return bidResponse, nil
+}
+
+// Builder builds a new instance of the Interactiveoffers adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+ bidder := &adapter{
+ endpoint: config.Endpoint,
+ }
+ return bidder, nil
+}
diff --git a/adapters/interactiveoffers/interactiveoffers_test.go b/adapters/interactiveoffers/interactiveoffers_test.go
new file mode 100644
index 00000000000..5746f123b41
--- /dev/null
+++ b/adapters/interactiveoffers/interactiveoffers_test.go
@@ -0,0 +1,20 @@
+package interactiveoffers
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderInteractiveoffers, config.Adapter{
+ Endpoint: "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "interactiveofferstest", bidder)
+}
diff --git a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json
new file mode 100644
index 00000000000..946289e5401
--- /dev/null
+++ b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json
@@ -0,0 +1,136 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ },{
+ "id": "test-imp-id-2",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ },{
+ "id": "test-imp-id-2",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "seat": "interactiveoffers",
+ "bid": [{
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "12345678",
+ "adm": "some-test-ad",
+ "cid": "987",
+ "crid": "12345678",
+ "h": 250,
+ "w": 300
+ },{
+ "id": "randomid2",
+ "impid": "test-imp-id-2",
+ "price": 0.500000,
+ "adid": "12345678",
+ "adm": "some-test-ad",
+ "cid": "987",
+ "crid": "12345678",
+ "h": 250,
+ "w": 300
+ }]
+ }],
+ "cur": "USD"
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "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"
+ },{
+ "bid": {
+ "id": "randomid2",
+ "impid": "test-imp-id-2",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "12345678",
+ "cid": "987",
+ "crid": "12345678",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json
new file mode 100644
index 00000000000..2f49d5451c8
--- /dev/null
+++ b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json
@@ -0,0 +1,87 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "seat": "interactiveoffers",
+ "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": "USD"
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "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"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/interactiveoffers/interactiveofferstest/params/race/banner.json b/adapters/interactiveoffers/interactiveofferstest/params/race/banner.json
new file mode 100644
index 00000000000..d81c02a5dc3
--- /dev/null
+++ b/adapters/interactiveoffers/interactiveofferstest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+ "pubid": 35
+}
diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json
new file mode 100644
index 00000000000..0d56311a188
--- /dev/null
+++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json
@@ -0,0 +1,55 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }],
+
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": []
+}
diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json
new file mode 100644
index 00000000000..9aaf12cb239
--- /dev/null
+++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json
@@ -0,0 +1,60 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 400,
+ "body": ""
+ }
+ }],
+
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json
new file mode 100644
index 00000000000..222be912a92
--- /dev/null
+++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json
@@ -0,0 +1,60 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 202,
+ "body": ""
+ }
+ }],
+
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 202. Run with request.debug = 1 for more info.",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json
new file mode 100644
index 00000000000..7ae16d4a95a
--- /dev/null
+++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json
@@ -0,0 +1,60 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "pubid": 35
+ }
+ }
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": ""
+ }
+ }],
+
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/interactiveoffers/params_test.go b/adapters/interactiveoffers/params_test.go
new file mode 100644
index 00000000000..754be842a80
--- /dev/null
+++ b/adapters/interactiveoffers/params_test.go
@@ -0,0 +1,44 @@
+package interactiveoffers
+
+import (
+ "encoding/json"
+ "github.com/prebid/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.BidderInteractiveoffers, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected interactiveoffers 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.BidderInteractiveoffers, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"pubid":35}`,
+}
+
+var invalidParams = []string{
+ `{"pubid":"35"}`,
+ `{"pubId":35}`,
+ `{"PubId":35}`,
+ `{}`,
+}
diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go
index f8903008328..b251ec0f736 100644
--- a/adapters/ix/ix.go
+++ b/adapters/ix/ix.go
@@ -7,7 +7,10 @@ import (
"fmt"
"io/ioutil"
"net/http"
+ "sort"
+ "github.com/mxmCherry/openrtb/v15/native1"
+ native1response "github.com/mxmCherry/openrtb/v15/native1/response"
"github.com/mxmCherry/openrtb/v15/openrtb2"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
@@ -400,7 +403,7 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque
for _, bid := range seatBid.Bid {
bidType, ok := impMediaType[bid.ImpID]
if !ok {
- errs = append(errs, fmt.Errorf("Unmatched impression id: %s.", bid.ImpID))
+ errs = append(errs, fmt.Errorf("unmatched impression id: %s", bid.ImpID))
}
var bidExtVideo *openrtb_ext.ExtBidPrebidVideo
@@ -409,8 +412,32 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque
unmarshalExtErr := json.Unmarshal(bid.Ext, &bidExt)
if unmarshalExtErr == nil && bidExt.Prebid != nil && bidExt.Prebid.Video != nil {
bidExtVideo = &openrtb_ext.ExtBidPrebidVideo{
- Duration: bidExt.Prebid.Video.Duration,
- PrimaryCategory: bidExt.Prebid.Video.PrimaryCategory,
+ Duration: bidExt.Prebid.Video.Duration,
+ }
+ if len(bid.Cat) == 0 {
+ bid.Cat = []string{bidExt.Prebid.Video.PrimaryCategory}
+ }
+ }
+ }
+
+ var bidNative1v1 *Native11Wrapper
+ if bidType == openrtb_ext.BidTypeNative {
+ err := json.Unmarshal([]byte(bid.AdM), &bidNative1v1)
+ if err == nil && len(bidNative1v1.Native.EventTrackers) > 0 {
+ mergeNativeImpTrackers(&bidNative1v1.Native)
+ if json, err := json.Marshal(bidNative1v1); err == nil {
+ bid.AdM = string(json)
+ }
+ }
+ }
+
+ var bidNative1v2 *native1response.Response
+ if bidType == openrtb_ext.BidTypeNative {
+ err := json.Unmarshal([]byte(bid.AdM), &bidNative1v2)
+ if err == nil && len(bidNative1v2.EventTrackers) > 0 {
+ mergeNativeImpTrackers(bidNative1v2)
+ if json, err := json.Marshal(bidNative1v2); err == nil {
+ bid.AdM = string(json)
}
}
}
@@ -442,3 +469,33 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters
}
return bidder, nil
}
+
+// native 1.2 to 1.1 tracker compatibility handling
+
+type Native11Wrapper struct {
+ Native native1response.Response `json:"native,omitempty"`
+}
+
+func mergeNativeImpTrackers(bidNative *native1response.Response) {
+
+ // create unique list of imp pixels urls from `imptrackers` and `eventtrackers`
+ uniqueImpPixels := map[string]struct{}{}
+ for _, v := range bidNative.ImpTrackers {
+ uniqueImpPixels[v] = struct{}{}
+ }
+
+ for _, v := range bidNative.EventTrackers {
+ if v.Event == native1.EventTypeImpression && v.Method == native1.EventTrackingMethodImage {
+ uniqueImpPixels[v.URL] = struct{}{}
+ }
+ }
+
+ // rewrite `imptrackers` with new deduped list of imp pixels
+ bidNative.ImpTrackers = make([]string, 0)
+ for k := range uniqueImpPixels {
+ bidNative.ImpTrackers = append(bidNative.ImpTrackers, k)
+ }
+
+ // sort so tests pass correctly
+ sort.Strings(bidNative.ImpTrackers)
+}
diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go
index bc70f3999df..d292273a92c 100644
--- a/adapters/ix/ix_test.go
+++ b/adapters/ix/ix_test.go
@@ -792,8 +792,8 @@ func TestIxMakeBidsWithCategoryDuration(t *testing.T) {
if bidResponse.Bids[0].BidVideo.Duration != expectedBidDuration {
t.Errorf("video duration should be set")
}
- if bidResponse.Bids[0].BidVideo.PrimaryCategory != expectedBidCategory {
- t.Errorf("video category should be set")
+ if bidResponse.Bids[0].Bid.Cat[0] != expectedBidCategory {
+ t.Errorf("bid category should be set")
}
if len(errors) != expectedErrorCount {
t.Errorf("should not have any errors, errors=%v", errors)
diff --git a/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json
new file mode 100644
index 00000000000..36a239987a6
--- /dev/null
+++ b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json
@@ -0,0 +1,104 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ },
+ "httpcalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://host/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"eventtrackers\":[{\"url\":\"https://example.com/imp-2.gif\",\"event\":1,\"method\":1},{\"url\":\"https://example.com/imp-3.gif\",\"event\":1,\"method\":1}],\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "cat": [
+ "IAB3-1"
+ ],
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\",\"https://example.com/imp-3.gif\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"https://example.com/imp-2.gif\"},{\"event\":1,\"method\":1,\"url\":\"https://example.com/imp-3.gif\"}]}",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "cat": [
+ "IAB3-1"
+ ],
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/ix/ixtest/supplemental/bad-imp-id.json b/adapters/ix/ixtest/supplemental/bad-imp-id.json
index 0b852c85d2b..1ca053b674e 100644
--- a/adapters/ix/ixtest/supplemental/bad-imp-id.json
+++ b/adapters/ix/ixtest/supplemental/bad-imp-id.json
@@ -111,7 +111,7 @@
],
"expectedMakeBidsErrors": [
{
- "value": "Unmatched impression id: bad-imp-id.",
+ "value": "unmatched impression id: bad-imp-id",
"comparison": "literal"
}
]
diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json
new file mode 100644
index 00000000000..4cf314e742f
--- /dev/null
+++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json
@@ -0,0 +1,104 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ },
+ "httpcalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://host/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"],\"eventtrackers\":[]}}",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "cat": [
+ "IAB3-1"
+ ],
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"],\"eventtrackers\":[]}}",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "cat": [
+ "IAB3-1"
+ ],
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json
new file mode 100644
index 00000000000..d8c78a5cbca
--- /dev/null
+++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json
@@ -0,0 +1,104 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ },
+ "httpcalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://host/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}}",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "cat": [
+ "IAB3-1"
+ ],
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}}",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "cat": [
+ "IAB3-1"
+ ],
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/ix/ixtest/supplemental/native-missing.json b/adapters/ix/ixtest/supplemental/native-missing.json
new file mode 100644
index 00000000000..ec2108ce5d1
--- /dev/null
+++ b/adapters/ix/ixtest/supplemental/native-missing.json
@@ -0,0 +1,104 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ },
+ "httpcalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://host/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "native": {
+ "request": "{}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "{\"native\":{}}",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "cat": [
+ "IAB3-1"
+ ],
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "{\"native\":{}}",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "cat": [
+ "IAB3-1"
+ ],
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/ix/params_test.go b/adapters/ix/params_test.go
new file mode 100644
index 00000000000..9246a43a725
--- /dev/null
+++ b/adapters/ix/params_test.go
@@ -0,0 +1,59 @@
+package ix
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/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.BidderIx, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected ix 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.BidderIx, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"siteid":"1234"}`,
+ `{"siteID":"12345"}`,
+ `{"siteId":"123456"}`,
+ `{"siteid":"1234567", "size": [640,480]}`,
+}
+
+var invalidParams = []string{
+ `{"siteid":""}`,
+ `{"siteID":""}`,
+ `{"siteId":""}`,
+ `{"siteid":"1234", "siteID":"12345"}`,
+ `{"siteid":"1234", "siteId":"123456"}`,
+ `{"siteid":123}`,
+ `{"siteids":"123"}`,
+ `{"notaparam":"123"}`,
+ `{"siteid":"123", "size": [1,2,3]}`,
+ `null`,
+ `true`,
+ `0`,
+ `abc`,
+ `[]`,
+ `{}`,
+}
diff --git a/adapters/kayzen/kayzen.go b/adapters/kayzen/kayzen.go
new file mode 100644
index 00000000000..1fcb9877e47
--- /dev/null
+++ b/adapters/kayzen/kayzen.go
@@ -0,0 +1,154 @@
+package kayzen
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "text/template"
+
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/macros"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+ endpoint template.Template
+}
+
+// Builder builds a new instance of the Kayzen adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+ template, err := template.New("endpointTemplate").Parse(config.Endpoint)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+ }
+
+ bidder := &adapter{
+ endpoint: *template,
+ }
+ return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) {
+ var kayzenExt *openrtb_ext.ExtKayzen
+ var err error
+
+ if len(request.Imp) > 0 {
+ kayzenExt, err = a.getImpressionExt(&(request.Imp[0]))
+ if err != nil {
+ errors = append(errors, err)
+ }
+ request.Imp[0].Ext = nil
+ } else {
+ errors = append(errors, &errortypes.BadInput{
+ Message: "Missing Imp Object",
+ })
+ }
+
+ if len(errors) > 0 {
+ return nil, errors
+ }
+
+ url, err := a.buildEndpointURL(kayzenExt)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ requestJSON, err := json.Marshal(request)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ return []*adapters.RequestData{{
+ Method: http.MethodPost,
+ Body: requestJSON,
+ Uri: url,
+ Headers: headers,
+ }}, nil
+}
+
+func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtKayzen, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: "Bidder extension not provided or can't be unmarshalled",
+ }
+ }
+ var kayzenExt openrtb_ext.ExtKayzen
+ if err := json.Unmarshal(bidderExt.Bidder, &kayzenExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: "Error while unmarshaling bidder extension",
+ }
+ }
+ return &kayzenExt, nil
+}
+
+func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtKayzen) (string, error) {
+ endpointParams := macros.EndpointTemplateParams{
+ ZoneID: params.Zone,
+ AccountID: params.Exchange,
+ }
+ return macros.ResolveMacros(a.endpoint, endpointParams)
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if response.StatusCode == http.StatusBadRequest {
+ err := &errortypes.BadInput{
+ Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
+ }
+ return nil, []error{err}
+ }
+
+ if response.StatusCode != http.StatusOK {
+ err := &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", response.StatusCode),
+ }
+ return nil, []error{err}
+ }
+
+ var bidResp openrtb2.BidResponse
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: "Bad Server Response",
+ }}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+
+ var errs []error
+ for _, seatBid := range bidResp.SeatBid {
+ for _, bid := range seatBid.Bid {
+ b := &adapters.TypedBid{
+ Bid: &bid,
+ BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp),
+ }
+ bidResponse.Bids = append(bidResponse.Bids, b)
+ }
+ }
+ return bidResponse, errs
+}
+
+func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType {
+ for _, imp := range imps {
+ if imp.ID == impID {
+ if imp.Banner != nil {
+ return openrtb_ext.BidTypeBanner
+ } else if imp.Video != nil {
+ return openrtb_ext.BidTypeVideo
+ } else if imp.Native != nil {
+ return openrtb_ext.BidTypeNative
+ }
+ }
+ }
+ return openrtb_ext.BidTypeBanner
+}
diff --git a/adapters/kayzen/kayzen_test.go b/adapters/kayzen/kayzen_test.go
new file mode 100644
index 00000000000..4cd07470ebb
--- /dev/null
+++ b/adapters/kayzen/kayzen_test.go
@@ -0,0 +1,29 @@
+package kayzen
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderKayzen, config.Adapter{
+ Endpoint: "https://example-{{.ZoneID}}.com/?exchange={{.AccountID}}",
+ })
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "kayzentest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+ _, buildErr := Builder(openrtb_ext.BidderKayzen, config.Adapter{
+ Endpoint: "{{Malformed}}"})
+
+ assert.Error(t, buildErr)
+}
diff --git a/adapters/kayzen/kayzentest/exemplary/banner-app.json b/adapters/kayzen/kayzentest/exemplary/banner-app.json
new file mode 100644
index 00000000000..157b79165cc
--- /dev/null
+++ b/adapters/kayzen/kayzentest/exemplary/banner-app.json
@@ -0,0 +1,142 @@
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w": 320,
+ "h": 50
+ }
+ }
+ ],
+ "app": {
+ "id": "123456789",
+ "name": "Awesome App",
+ "bundle": "com.app.awesome",
+ "domain": "awesomeapp.com",
+ "cat": [
+ "IAB22-1"
+ ],
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "1",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "type": "banner",
+ "seat": "kayzen-prebid"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids": [
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "1",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 320,
+ "h": 50
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/kayzen/kayzentest/exemplary/banner-web.json b/adapters/kayzen/kayzentest/exemplary/banner-web.json
new file mode 100644
index 00000000000..0d25d4fd981
--- /dev/null
+++ b/adapters/kayzen/kayzentest/exemplary/banner-web.json
@@ -0,0 +1,129 @@
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w":320,
+ "h":50
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w":320,
+ "h":50
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "1",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "type": "banner",
+ "seat": "kayzen-prebid"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids":[
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "1",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "crid": "20",
+ "adomain": [
+ "awesome.com"
+ ],
+ "w": 320,
+ "h": 50
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/kayzen/kayzentest/exemplary/native-app.json b/adapters/kayzen/kayzentest/exemplary/native-app.json
new file mode 100644
index 00000000000..ef43dab798c
--- /dev/null
+++ b/adapters/kayzen/kayzentest/exemplary/native-app.json
@@ -0,0 +1,137 @@
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native": {
+ "ver":"1.1",
+ "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native": {
+ "ver":"1.1",
+ "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+ }
+ }
+ ],
+ "app": {
+ "id": "123456789",
+ "name": "Awesome App",
+ "bundle": "com.app.awesome",
+ "domain": "awesomeapp.com",
+ "cat": [
+ "IAB22-1"
+ ],
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20"
+ }
+ ],
+ "type": "native",
+ "seat": "kayzen-prebid"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids":[
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "crid": "20",
+ "adomain": [
+ "awesome.com"
+ ]
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+ }
diff --git a/adapters/kayzen/kayzentest/exemplary/native-web.json b/adapters/kayzen/kayzentest/exemplary/native-web.json
new file mode 100644
index 00000000000..34050f989ad
--- /dev/null
+++ b/adapters/kayzen/kayzentest/exemplary/native-web.json
@@ -0,0 +1,124 @@
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native": {
+ "ver":"1.1",
+ "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native": {
+ "ver":"1.1",
+ "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20"
+ }
+ ],
+ "seat": "kayzen-prebid"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids":[
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "crid": "20",
+ "adomain": [
+ "awesome.com"
+ ]
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/kayzen/kayzentest/exemplary/video-app.json b/adapters/kayzen/kayzentest/exemplary/video-app.json
new file mode 100644
index 00000000000..f8744b638f2
--- /dev/null
+++ b/adapters/kayzen/kayzentest/exemplary/video-app.json
@@ -0,0 +1,150 @@
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 120,
+ "maxduration": 150,
+ "w": 640,
+ "h": 480
+ }
+ }
+ ],
+ "app": {
+ "id": "123456789",
+ "name": "Awesome App",
+ "bundle": "com.app.awesome",
+ "domain": "awesomeapp.com",
+ "cat": [
+ "IAB22-1"
+ ],
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 1280,
+ "h": 720
+ }
+ ],
+ "seat": "kayzen-prebid"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids":[
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "crid": "20",
+ "adomain": [
+ "awesome.com"
+ ],
+ "w": 1280,
+ "h": 720
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/kayzen/kayzentest/exemplary/video-web.json b/adapters/kayzen/kayzentest/exemplary/video-web.json
new file mode 100644
index 00000000000..7a507ca3cd9
--- /dev/null
+++ b/adapters/kayzen/kayzentest/exemplary/video-web.json
@@ -0,0 +1,149 @@
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 120,
+ "maxduration": 150,
+ "w": 640,
+ "h": 480
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "awesome-resp-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 1280,
+ "h": 720,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ }
+ ],
+ "seat": "kayzen-prebid"
+ }
+ ],
+ "cur": "USD",
+ "ext": {
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids": [
+ {
+ "bid": {
+ "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+ "impid": "some-impression-id",
+ "price": 3.5,
+ "adm": "awesome-markup",
+ "adomain": [
+ "awesome.com"
+ ],
+ "crid": "20",
+ "w": 1280,
+ "h": 720,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/adapters/kayzen/kayzentest/params/race/banner.json b/adapters/kayzen/kayzentest/params/race/banner.json
new file mode 100644
index 00000000000..bdb2c9de976
--- /dev/null
+++ b/adapters/kayzen/kayzentest/params/race/banner.json
@@ -0,0 +1,5 @@
+{
+ "zone": "dc",
+ "exchange": "ex"
+}
+
diff --git a/adapters/kayzen/kayzentest/params/race/native.json b/adapters/kayzen/kayzentest/params/race/native.json
new file mode 100644
index 00000000000..bdb2c9de976
--- /dev/null
+++ b/adapters/kayzen/kayzentest/params/race/native.json
@@ -0,0 +1,5 @@
+{
+ "zone": "dc",
+ "exchange": "ex"
+}
+
diff --git a/adapters/kayzen/kayzentest/params/race/video.json b/adapters/kayzen/kayzentest/params/race/video.json
new file mode 100644
index 00000000000..bdb2c9de976
--- /dev/null
+++ b/adapters/kayzen/kayzentest/params/race/video.json
@@ -0,0 +1,5 @@
+{
+ "zone": "dc",
+ "exchange": "ex"
+}
+
diff --git a/adapters/kayzen/kayzentest/supplemental/invalid-ext-object.json b/adapters/kayzen/kayzentest/supplemental/invalid-ext-object.json
new file mode 100644
index 00000000000..2dda068b33e
--- /dev/null
+++ b/adapters/kayzen/kayzentest/supplemental/invalid-ext-object.json
@@ -0,0 +1,29 @@
+{
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Bidder extension not provided or can't be unmarshalled",
+ "comparison": "literal"
+ }
+ ],
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "tagid": "my-adcode",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": ""
+ }
+ ],
+ "site": {
+ "page": "test.com"
+ }
+ },
+ "httpCalls": []
+}
diff --git a/adapters/kayzen/kayzentest/supplemental/invalid-response.json b/adapters/kayzen/kayzentest/supplemental/invalid-response.json
new file mode 100644
index 00000000000..4963ed677f9
--- /dev/null
+++ b/adapters/kayzen/kayzentest/supplemental/invalid-response.json
@@ -0,0 +1,101 @@
+
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w":320,
+ "h":50
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "w":320,
+ "h":50
+ }
+ }
+ ],
+ "app": {
+ "id": "123456789",
+ "name": "Awesome App",
+ "bundle": "com.app.awesome",
+ "domain": "awesomeapp.com",
+ "cat": [
+ "IAB22-1"
+ ],
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": "invalid response"
+ }
+ }],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Bad Server Response",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/kayzen/kayzentest/supplemental/requires-imp-object.json b/adapters/kayzen/kayzentest/supplemental/requires-imp-object.json
new file mode 100644
index 00000000000..1fcf1b48c5a
--- /dev/null
+++ b/adapters/kayzen/kayzentest/supplemental/requires-imp-object.json
@@ -0,0 +1,16 @@
+{
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Missing Imp Object",
+ "comparison": "literal"
+ }
+ ],
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [],
+ "site": {
+ "page": "test.com"
+ }
+ },
+ "httpCalls": []
+}
diff --git a/adapters/kayzen/kayzentest/supplemental/status-code-bad-request.json b/adapters/kayzen/kayzentest/supplemental/status-code-bad-request.json
new file mode 100644
index 00000000000..8b9e4d06fbf
--- /dev/null
+++ b/adapters/kayzen/kayzentest/supplemental/status-code-bad-request.json
@@ -0,0 +1,91 @@
+
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 120,
+ "maxduration": 150,
+ "w": 640,
+ "h": 480
+ }
+ }
+ ],
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/kayzen/kayzentest/supplemental/status-code-no-content.json b/adapters/kayzen/kayzentest/supplemental/status-code-no-content.json
new file mode 100644
index 00000000000..69e64c37d52
--- /dev/null
+++ b/adapters/kayzen/kayzentest/supplemental/status-code-no-content.json
@@ -0,0 +1,72 @@
+
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 120,
+ "maxduration": 150,
+ "w": 640,
+ "h": 480
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": []
+}
diff --git a/adapters/kayzen/kayzentest/supplemental/status-code-other-error.json b/adapters/kayzen/kayzentest/supplemental/status-code-other-error.json
new file mode 100644
index 00000000000..b487b91dfa2
--- /dev/null
+++ b/adapters/kayzen/kayzentest/supplemental/status-code-other-error.json
@@ -0,0 +1,77 @@
+
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "zone": "dc",
+ "exchange": "ex"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://example-dc.com/?exchange=ex",
+ "body": {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 120,
+ "maxduration": 150,
+ "w": 640,
+ "h": 480
+ }
+ }
+ ],
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 306
+ }
+ }],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 306. Run with request.debug = 1 for more info.",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/kayzen/params_test.go b/adapters/kayzen/params_test.go
new file mode 100644
index 00000000000..07bd0851a97
--- /dev/null
+++ b/adapters/kayzen/params_test.go
@@ -0,0 +1,55 @@
+package kayzen
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+var validParams = []string{
+ `{ "zone": "dc", "exchange": "ex" }`,
+}
+
+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.BidderKayzen, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected Kayzen params: %s", validParam)
+ }
+ }
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `4.2`,
+ `[]`,
+ `{}`,
+ `{ "key": 2 }`,
+ `{ "anyparam": "anyvalue" }`,
+ `{ "zone": "dc" }`,
+ `{ "exchange": "ex" }`,
+ `{ "exchange": "", "zone" : "" }`,
+ `{ "exchange": "ex", "zone" : "" }`,
+ `{ "exchange": "", "zone" : "dc" }`,
+}
+
+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.BidderKayzen, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
diff --git a/adapters/lifestreet/lifestreet.go b/adapters/lifestreet/lifestreet.go
deleted file mode 100644
index 14f6931751a..00000000000
--- a/adapters/lifestreet/lifestreet.go
+++ /dev/null
@@ -1,219 +0,0 @@
-package lifestreet
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "strings"
-
- "github.com/mxmCherry/openrtb/v15/openrtb2"
- "github.com/prebid/prebid-server/openrtb_ext"
-
- "github.com/prebid/prebid-server/adapters"
- "github.com/prebid/prebid-server/errortypes"
- "github.com/prebid/prebid-server/pbs"
- "golang.org/x/net/context/ctxhttp"
-)
-
-type LifestreetAdapter struct {
- http *adapters.HTTPAdapter
- URI string
-}
-
-// used for cookies and such
-func (a *LifestreetAdapter) Name() string {
- return "lifestreet"
-}
-
-func (a *LifestreetAdapter) SkipNoCookies() bool {
- return false
-}
-
-// parameters for Lifestreet adapter.
-type lifestreetParams struct {
- SlotTag string `json:"slot_tag"`
-}
-
-func (a *LifestreetAdapter) callOne(ctx context.Context, req *pbs.PBSRequest, reqJSON bytes.Buffer) (result adapters.CallOneResult, err error) {
- httpReq, err := http.NewRequest("POST", a.URI, &reqJSON)
- httpReq.Header.Add("Content-Type", "application/json;charset=utf-8")
- httpReq.Header.Add("Accept", "application/json")
-
- lsmResp, e := ctxhttp.Do(ctx, a.http.Client, httpReq)
- if e != nil {
- err = e
- return
- }
-
- defer lsmResp.Body.Close()
- body, _ := ioutil.ReadAll(lsmResp.Body)
- result.ResponseBody = string(body)
-
- result.StatusCode = lsmResp.StatusCode
-
- if lsmResp.StatusCode == 204 {
- return
- }
-
- if lsmResp.StatusCode != 200 {
- err = fmt.Errorf("HTTP status %d; body: %s", lsmResp.StatusCode, result.ResponseBody)
- return
- }
-
- var bidResp openrtb2.BidResponse
- err = json.Unmarshal(body, &bidResp)
- if err != nil {
- return
- }
- if len(bidResp.SeatBid) == 0 || len(bidResp.SeatBid[0].Bid) == 0 {
- return
- }
- bid := bidResp.SeatBid[0].Bid[0]
-
- t := openrtb_ext.BidTypeBanner
-
- if bid.Ext != nil {
- var e openrtb_ext.ExtBid
- err = json.Unmarshal(bid.Ext, &e)
- if err != nil {
- return
- }
- t = e.Prebid.Type
- }
-
- result.Bid = &pbs.PBSBid{
- AdUnitCode: bid.ImpID,
- Price: bid.Price,
- Adm: bid.AdM,
- Creative_id: bid.CrID,
- Width: bid.W,
- Height: bid.H,
- DealId: bid.DealID,
- NURL: bid.NURL,
- CreativeMediaType: string(t),
- }
- return
-}
-
-func (a *LifestreetAdapter) MakeOpenRtbBidRequest(req *pbs.PBSRequest, bidder *pbs.PBSBidder, slotTag string, mtype pbs.MediaType, unitInd int) (openrtb2.BidRequest, error) {
- lsReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), []pbs.MediaType{mtype})
-
- if err != nil {
- return openrtb2.BidRequest{}, err
- }
-
- if lsReq.Imp != nil && len(lsReq.Imp) > 0 {
- lsReq.Imp = lsReq.Imp[unitInd : unitInd+1]
-
- if lsReq.Imp[0].Banner != nil {
- lsReq.Imp[0].Banner.Format = nil
- }
- lsReq.Imp[0].TagID = slotTag
-
- return lsReq, nil
- } else {
- return lsReq, &errortypes.BadInput{
- Message: "No supported impressions",
- }
- }
-}
-
-func (a *LifestreetAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) {
- requests := make([]bytes.Buffer, len(bidder.AdUnits)*2)
- reqIndex := 0
- for i, unit := range bidder.AdUnits {
- var params lifestreetParams
- err := json.Unmarshal(unit.Params, ¶ms)
- if err != nil {
- return nil, err
- }
- if params.SlotTag == "" {
- return nil, &errortypes.BadInput{
- Message: "Missing slot_tag param",
- }
- }
- s := strings.Split(params.SlotTag, ".")
- if len(s) != 2 {
- return nil, &errortypes.BadInput{
- Message: fmt.Sprintf("Invalid slot_tag param '%s'", params.SlotTag),
- }
- }
-
- // BANNER
- lsReqB, err := a.MakeOpenRtbBidRequest(req, bidder, params.SlotTag, pbs.MEDIA_TYPE_BANNER, i)
- if err == nil {
- err = json.NewEncoder(&requests[reqIndex]).Encode(lsReqB)
- reqIndex = reqIndex + 1
- if err != nil {
- return nil, err
- }
- }
-
- // VIDEO
- lsReqV, err := a.MakeOpenRtbBidRequest(req, bidder, params.SlotTag, pbs.MEDIA_TYPE_VIDEO, i)
- if err == nil {
- err = json.NewEncoder(&requests[reqIndex]).Encode(lsReqV)
- reqIndex = reqIndex + 1
- if err != nil {
- return nil, err
- }
- }
- }
-
- ch := make(chan adapters.CallOneResult)
- for i := range bidder.AdUnits {
- go func(bidder *pbs.PBSBidder, reqJSON bytes.Buffer) {
- result, err := a.callOne(ctx, req, reqJSON)
- result.Error = err
- if result.Bid != nil {
- result.Bid.BidderCode = bidder.BidderCode
- result.Bid.BidID = bidder.LookupBidID(result.Bid.AdUnitCode)
- if result.Bid.BidID == "" {
- result.Error = &errortypes.BadServerResponse{
- Message: fmt.Sprintf("Unknown ad unit code '%s'", result.Bid.AdUnitCode),
- }
- result.Bid = nil
- }
- }
- ch <- result
- }(bidder, requests[i])
- }
-
- var err error
-
- bids := make(pbs.PBSBidSlice, 0)
- for i := 0; i < len(bidder.AdUnits); i++ {
- result := <-ch
- if result.Bid != nil {
- bids = append(bids, result.Bid)
- }
- if req.IsDebug {
- debug := &pbs.BidderDebug{
- RequestURI: a.URI,
- RequestBody: requests[i].String(),
- StatusCode: result.StatusCode,
- ResponseBody: result.ResponseBody,
- }
- bidder.Debug = append(bidder.Debug, debug)
- }
- if result.Error != nil {
- err = result.Error
- }
- }
-
- if len(bids) == 0 {
- return nil, err
- }
- return bids, nil
-}
-
-func NewLifestreetLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *LifestreetAdapter {
- a := adapters.NewHTTPAdapter(config)
- return &LifestreetAdapter{
- http: a,
- URI: endpoint,
- }
-}
diff --git a/adapters/lifestreet/lifestreet_test.go b/adapters/lifestreet/lifestreet_test.go
deleted file mode 100644
index 5c4f47fdff9..00000000000
--- a/adapters/lifestreet/lifestreet_test.go
+++ /dev/null
@@ -1,291 +0,0 @@
-package lifestreet
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "testing"
- "time"
-
- "github.com/mxmCherry/openrtb/v15/openrtb2"
- "github.com/prebid/prebid-server/cache/dummycache"
- "github.com/prebid/prebid-server/pbs"
- "github.com/prebid/prebid-server/usersync"
-
- "fmt"
-
- "github.com/prebid/prebid-server/adapters"
- "github.com/prebid/prebid-server/config"
-)
-
-type lsTagInfo struct {
- code string
- slotTag string
- bid float64
- content string
-}
-
-type lsBidInfo struct {
- appBundle string
- deviceIP string
- deviceUA string
- deviceMake string
- deviceModel string
- deviceConnectiontype int8
- deviceIfa string
- tags []lsTagInfo
- referrer string
- width uint64
- height uint64
- delay time.Duration
-}
-
-var lsdata lsBidInfo
-
-func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) {
- defer r.Body.Close()
- body, err := ioutil.ReadAll(r.Body)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- var breq openrtb2.BidRequest
- err = json.Unmarshal(body, &breq)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- if breq.App == nil {
- http.Error(w, fmt.Sprintf("No app object sent"), http.StatusInternalServerError)
- return
- }
- if breq.App.Bundle != lsdata.appBundle {
- http.Error(w, fmt.Sprintf("Bundle '%s' doesn't match '%s", breq.App.Bundle, lsdata.appBundle), http.StatusInternalServerError)
- return
- }
- if breq.Device.UA != lsdata.deviceUA {
- http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s", breq.Device.UA, lsdata.deviceUA), http.StatusInternalServerError)
- return
- }
- if breq.Device.IP != lsdata.deviceIP {
- http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s", breq.Device.IP, lsdata.deviceIP), http.StatusInternalServerError)
- return
- }
- if breq.Device.Make != lsdata.deviceMake {
- http.Error(w, fmt.Sprintf("Make '%s' doesn't match '%s", breq.Device.Make, lsdata.deviceMake), http.StatusInternalServerError)
- return
- }
- if breq.Device.Model != lsdata.deviceModel {
- http.Error(w, fmt.Sprintf("Model '%s' doesn't match '%s", breq.Device.Model, lsdata.deviceModel), http.StatusInternalServerError)
- return
- }
- if *breq.Device.ConnectionType != openrtb2.ConnectionType(lsdata.deviceConnectiontype) {
- http.Error(w, fmt.Sprintf("Connectiontype '%d' doesn't match '%d", breq.Device.ConnectionType, lsdata.deviceConnectiontype), http.StatusInternalServerError)
- return
- }
- if breq.Device.IFA != lsdata.deviceIfa {
- http.Error(w, fmt.Sprintf("IFA '%s' doesn't match '%s", breq.Device.IFA, lsdata.deviceIfa), http.StatusInternalServerError)
- return
- }
- if len(breq.Imp) != 1 {
- http.Error(w, fmt.Sprintf("Wrong number of imp objects sent: %d", len(breq.Imp)), http.StatusInternalServerError)
- return
- }
- var bid *openrtb2.Bid
- for _, tag := range lsdata.tags {
- if breq.Imp[0].Banner == nil {
- http.Error(w, fmt.Sprintf("No banner object sent"), http.StatusInternalServerError)
- return
- }
- if *breq.Imp[0].Banner.W != int64(lsdata.width) || *breq.Imp[0].Banner.H != int64(lsdata.height) {
- http.Error(w, fmt.Sprintf("Size '%dx%d' doesn't match '%dx%d", breq.Imp[0].Banner.W, breq.Imp[0].Banner.H, lsdata.width, lsdata.height), http.StatusInternalServerError)
- return
- }
- if breq.Imp[0].TagID == tag.slotTag {
- bid = &openrtb2.Bid{
- ID: "random-id",
- ImpID: breq.Imp[0].ID,
- Price: tag.bid,
- AdM: tag.content,
- W: int64(lsdata.width),
- H: int64(lsdata.height),
- }
- }
- }
- if bid == nil {
- http.Error(w, fmt.Sprintf("Slot tag '%s' not found", breq.Imp[0].TagID), http.StatusInternalServerError)
- return
- }
-
- resp := openrtb2.BidResponse{
- ID: "2345676337",
- BidID: "975537589956",
- Cur: "USD",
- SeatBid: []openrtb2.SeatBid{
- {
- Seat: "LSM",
- Bid: []openrtb2.Bid{*bid},
- },
- },
- }
-
- if lsdata.delay > 0 {
- <-time.After(lsdata.delay)
- }
-
- js, err := json.Marshal(resp)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- w.Header().Set("Content-Type", "application/json")
- w.Write(js)
-}
-
-func TestLifestreetBasicResponse(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(DummyLifestreetServer))
- defer server.Close()
-
- lsdata = lsBidInfo{
- appBundle: "AppNexus.PrebidMobileDemo",
- deviceIP: "111.111.111.111",
- deviceUA: "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E8301",
- deviceMake: "Apple",
- deviceModel: "x86_64",
- deviceConnectiontype: 1,
- deviceIfa: "6F3EA622-C2EE-4449-A97A-AE986D080C08",
- tags: make([]lsTagInfo, 2),
- referrer: "http://test.com",
- width: 320,
- height: 480,
- }
- lsdata.tags[0] = lsTagInfo{
- code: "first-tag",
- slotTag: "slot123.123",
- bid: 2.44,
- }
- lsdata.tags[1] = lsTagInfo{
- code: "second-tag",
- slotTag: "slot122.122",
- bid: 1.11,
- }
-
- conf := *adapters.DefaultHTTPAdapterConfig
- an := NewLifestreetLegacyAdapter(&conf, "https://prebid.s2s.lfstmedia.com/adrequest")
- an.URI = server.URL
-
- pbin := pbs.PBSRequest{
- AdUnits: make([]pbs.AdUnit, 2),
- App: &openrtb2.App{
- Bundle: lsdata.appBundle,
- },
- Device: &openrtb2.Device{
- UA: lsdata.deviceUA,
- IP: lsdata.deviceIP,
- Make: lsdata.deviceMake,
- Model: lsdata.deviceModel,
- ConnectionType: openrtb2.ConnectionType(lsdata.deviceConnectiontype).Ptr(),
- IFA: lsdata.deviceIfa,
- },
- }
- for i, tag := range lsdata.tags {
- pbin.AdUnits[i] = pbs.AdUnit{
- Code: tag.code,
- Sizes: []openrtb2.Format{
- {
- W: int64(lsdata.width),
- H: int64(lsdata.height),
- },
- },
- Bids: []pbs.Bids{
- {
- BidderCode: "lifestreet",
- BidID: fmt.Sprintf("random-id-from-pbjs-%d", i),
- Params: json.RawMessage(fmt.Sprintf("{\"slot_tag\": \"%s\"}", tag.slotTag)),
- },
- },
- }
- }
-
- body := new(bytes.Buffer)
- err := json.NewEncoder(body).Encode(pbin)
- if err != nil {
- t.Fatalf("Json encoding failed: %v", err)
- }
-
- fmt.Println("body", body)
-
- req := httptest.NewRequest("POST", server.URL, body)
- req.Header.Add("User-Agent", lsdata.deviceUA)
- req.Header.Add("Referer", lsdata.referrer)
- req.Header.Add("X-Real-IP", lsdata.deviceIP)
-
- pc := usersync.ParsePBSCookieFromRequest(req, &config.HostCookie{})
- fakewriter := httptest.NewRecorder()
-
- pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
- req.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie"))
-
- cacheClient, _ := dummycache.New()
- hcc := config.HostCookie{}
- pbReq, err := pbs.ParsePBSRequest(req, &config.AuctionTimeouts{
- Default: 2000,
- Max: 2000,
- }, cacheClient, &hcc)
- if err != nil {
- t.Fatalf("ParsePBSRequest failed: %v", err)
- }
- if len(pbReq.Bidders) != 1 {
- t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders))
- }
- if pbReq.Bidders[0].BidderCode != "lifestreet" {
- t.Fatalf("ParsePBSRequest returned invalid bidder")
- }
-
- ctx := context.TODO()
- bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0])
- if err != nil {
- t.Fatalf("Should not have gotten an error: %v", err)
- }
- if len(bids) != 2 {
- t.Fatalf("Received %d bids instead of 2", len(bids))
- }
- for _, bid := range bids {
- matched := false
- for _, tag := range lsdata.tags {
- if bid.AdUnitCode == tag.code {
- matched = true
- if bid.BidderCode != "lifestreet" {
- t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode)
- }
- if bid.Price != tag.bid {
- t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid)
- }
- if bid.Width != int64(lsdata.width) || bid.Height != int64(lsdata.height) {
- t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, lsdata.width, lsdata.height)
- }
- if bid.Adm != tag.content {
- t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content)
- }
- }
- }
- if !matched {
- t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode)
- }
- }
-
- // same test but with request timing out
- lsdata.delay = 5 * time.Millisecond
- ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
- defer cancel()
-
- bids, err = an.Call(ctx, pbReq, pbReq.Bidders[0])
- if err == nil {
- t.Fatalf("Should have gotten a timeout error: %v", err)
- }
-}
diff --git a/adapters/lifestreet/lifestreettest/params/race/banner.json b/adapters/lifestreet/lifestreettest/params/race/banner.json
deleted file mode 100644
index c746cc15630..00000000000
--- a/adapters/lifestreet/lifestreettest/params/race/banner.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "slot_tag": "slot166704"
-}
diff --git a/adapters/lifestreet/lifestreettest/params/race/video.json b/adapters/lifestreet/lifestreettest/params/race/video.json
deleted file mode 100644
index 7103cf63631..00000000000
--- a/adapters/lifestreet/lifestreettest/params/race/video.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "slot_tag": "slot1227631"
-}
diff --git a/adapters/lifestreet/usersync.go b/adapters/lifestreet/usersync.go
deleted file mode 100644
index f5300ebaa90..00000000000
--- a/adapters/lifestreet/usersync.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package lifestreet
-
-import (
- "text/template"
-
- "github.com/prebid/prebid-server/adapters"
- "github.com/prebid/prebid-server/usersync"
-)
-
-func NewLifestreetSyncer(temp *template.Template) usersync.Usersyncer {
- return adapters.NewSyncer("lifestreet", temp, adapters.SyncTypeRedirect)
-}
diff --git a/adapters/lifestreet/usersync_test.go b/adapters/lifestreet/usersync_test.go
deleted file mode 100644
index e41217fe10f..00000000000
--- a/adapters/lifestreet/usersync_test.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package lifestreet
-
-import (
- "testing"
- "text/template"
-
- "github.com/prebid/prebid-server/privacy"
- "github.com/prebid/prebid-server/privacy/gdpr"
- "github.com/stretchr/testify/assert"
-)
-
-func TestLifestreetSyncer(t *testing.T) {
- syncURL := "//ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl=localhost%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24"
- syncURLTemplate := template.Must(
- template.New("sync-template").Parse(syncURL),
- )
-
- syncer := NewLifestreetSyncer(syncURLTemplate)
- syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
- GDPR: gdpr.Policy{
- Signal: "0",
- },
- })
-
- assert.NoError(t, err)
- assert.Equal(t, "//ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl=localhost%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24%24visitor_cookie%24%24", syncInfo.URL)
- assert.Equal(t, "redirect", syncInfo.Type)
- assert.Equal(t, false, syncInfo.SupportCORS)
-}
diff --git a/adapters/madvertise/madvertise.go b/adapters/madvertise/madvertise.go
new file mode 100644
index 00000000000..dfc996d2497
--- /dev/null
+++ b/adapters/madvertise/madvertise.go
@@ -0,0 +1,163 @@
+package madvertise
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "text/template"
+
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/macros"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+ endpointTemplate template.Template
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+ template, err := template.New("endpointTemplate").Parse(config.Endpoint)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+ }
+
+ bidder := &adapter{
+ endpointTemplate: *template,
+ }
+
+ return bidder, nil
+}
+
+func getHeaders(request *openrtb2.BidRequest) http.Header {
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+ headers.Add("X-Openrtb-Version", "2.5")
+
+ if request.Device != nil {
+ if len(request.Device.UA) > 0 {
+ headers.Add("User-Agent", request.Device.UA)
+ }
+
+ if len(request.Device.IP) > 0 {
+ headers.Add("X-Forwarded-For", request.Device.IP)
+ }
+ }
+
+ return headers
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ zoneID := ""
+ for _, imp := range request.Imp {
+ madvertiseExt, err := getImpressionExt(imp)
+ if err != nil {
+ return nil, []error{err}
+ }
+ if len(madvertiseExt.ZoneID) < 7 {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("The minLength of zone ID is 7; ImpID=%s", imp.ID),
+ }}
+ }
+ if zoneID == "" {
+ zoneID = madvertiseExt.ZoneID
+ } else if zoneID != madvertiseExt.ZoneID {
+ return nil, []error{&errortypes.BadInput{
+ Message: "There must be only one zone ID",
+ }}
+ }
+ }
+ url, err := a.buildEndpointURL(zoneID)
+ if err != nil {
+ return nil, []error{err}
+ }
+ requestJSON, err := json.Marshal(request)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ requestData := &adapters.RequestData{
+ Method: "POST",
+ Uri: url,
+ Body: requestJSON,
+ Headers: getHeaders(request),
+ }
+
+ return []*adapters.RequestData{requestData}, nil
+}
+
+func getImpressionExt(imp openrtb2.Imp) (*openrtb_ext.ExtImpMadvertise, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("%s; ImpID=%s", err.Error(), imp.ID),
+ }
+ }
+ var madvertiseExt openrtb_ext.ExtImpMadvertise
+ if err := json.Unmarshal(bidderExt.Bidder, &madvertiseExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("%s; ImpID=%s", err.Error(), imp.ID),
+ }
+ }
+ if madvertiseExt.ZoneID == "" {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("ext.bidder.zoneId not provided; ImpID=%s", imp.ID),
+ }
+ }
+
+ return &madvertiseExt, nil
+}
+
+func (a *adapter) buildEndpointURL(zoneID string) (string, error) {
+ endpointParams := macros.EndpointTemplateParams{ZoneID: zoneID}
+ return macros.ResolveMacros(a.endpointTemplate, endpointParams)
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if responseData.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if responseData.StatusCode != http.StatusOK {
+ err := &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", responseData.StatusCode),
+ }
+ return nil, []error{err}
+ }
+
+ var response openrtb2.BidResponse
+ if err := json.Unmarshal(responseData.Body, &response); err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+ bidResponse.Currency = response.Cur
+ for _, seatBid := range response.SeatBid {
+ for i := range seatBid.Bid {
+ bid := seatBid.Bid[i]
+ bidMediaType := getMediaTypeForBid(bid.Attr)
+ b := &adapters.TypedBid{
+ Bid: &bid,
+ BidType: bidMediaType,
+ }
+ bidResponse.Bids = append(bidResponse.Bids, b)
+ }
+ }
+ return bidResponse, nil
+}
+
+func getMediaTypeForBid(attr []openrtb2.CreativeAttribute) openrtb_ext.BidType {
+ for i := 0; i < len(attr); i++ {
+ if attr[i] == openrtb2.CreativeAttribute(16) {
+ return openrtb_ext.BidTypeVideo
+ } else if attr[i] == openrtb2.CreativeAttribute(6) {
+ return openrtb_ext.BidTypeVideo
+ } else if attr[i] == openrtb2.CreativeAttribute(7) {
+ return openrtb_ext.BidTypeVideo
+ }
+ }
+ return openrtb_ext.BidTypeBanner
+}
diff --git a/adapters/madvertise/madvertise_test.go b/adapters/madvertise/madvertise_test.go
new file mode 100644
index 00000000000..924f25c1f8e
--- /dev/null
+++ b/adapters/madvertise/madvertise_test.go
@@ -0,0 +1,26 @@
+package madvertise
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+ _, buildErr := Builder(openrtb_ext.BidderMadvertise, config.Adapter{
+ Endpoint: "{{Malformed}}"})
+
+ assert.Error(t, buildErr)
+}
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderMadvertise, config.Adapter{
+ Endpoint: "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}"})
+
+ assert.NoError(t, buildErr, "Builder returned unexpected error %v", buildErr)
+
+ adapterstest.RunJSONBidderTest(t, "madvertisetest", bidder)
+}
diff --git a/adapters/madvertise/madvertisetest/exemplary/simple-banner.json b/adapters/madvertise/madvertisetest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..0c277a06b62
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/exemplary/simple-banner.json
@@ -0,0 +1,203 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 1,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack"
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "instl": 0,
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "pos": 1
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Openrtb-Version": [
+ "2.5"
+ ],
+ "User-Agent": [
+ "test-user-agent"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "body": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 1,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack"
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "pos": 1
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "burl": "https://burl.com",
+ "adomain": [
+ "madvertise.com"
+ ],
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "seat": "adman"
+ }
+ ],
+ "cur": "EUR"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "burl": "https://burl.com",
+ "adomain": [
+ "madvertise.com"
+ ],
+ "w": 320,
+ "h": 50
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/exemplary/simple-video.json b/adapters/madvertise/madvertisetest/exemplary/simple-video.json
new file mode 100644
index 00000000000..a387c06eeac
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/exemplary/simple-video.json
@@ -0,0 +1,256 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 2,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack",
+ "cat": [
+ "IAB6",
+ "IAB9",
+ "IAB10",
+ "IAB12",
+ "IAB14",
+ "IAB20"
+ ]
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "instl": 0,
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 320,
+ "h": 480,
+ "minduration": 2,
+ "maxduration": 30,
+ "playbackmethod": [
+ 1,
+ 3
+ ],
+ "boxingallowed": 0,
+ "protocols": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "placement": 5
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/video"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://mobile.mng-ads.com/bidrequest/1111111/video",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Openrtb-Version": [
+ "2.5"
+ ],
+ "User-Agent": [
+ "test-user-agent"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "body": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 2,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack",
+ "cat": [
+ "IAB6",
+ "IAB9",
+ "IAB10",
+ "IAB12",
+ "IAB14",
+ "IAB20"
+ ]
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 320,
+ "h": 480,
+ "minduration": 2,
+ "maxduration": 30,
+ "playbackmethod": [
+ 1,
+ 3
+ ],
+ "protocols": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "placement": 5
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/video"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "burl": "https://burl.com",
+ "attr": [6],
+ "adomain": [
+ "madvertise.com"
+ ],
+ "w": 320,
+ "h": 480
+ }
+ ],
+ "seat": "adman"
+ }
+ ],
+ "cur": "EUR"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "burl": "https://burl.com",
+ "attr": [6],
+ "adomain": [
+ "madvertise.com"
+ ],
+ "w": 320,
+ "h": 480
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/params/race/banner.json b/adapters/madvertise/madvertisetest/params/race/banner.json
new file mode 100644
index 00000000000..45243896f71
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/params/race/banner.json
@@ -0,0 +1,4 @@
+{
+ "zoneId": "/1111111/banner"
+}
+
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/params/race/video.json b/adapters/madvertise/madvertisetest/params/race/video.json
new file mode 100644
index 00000000000..8d462a8e8d7
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+ "zoneId": "/1111111/video"
+}
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/supplemental/display-site-test.json b/adapters/madvertise/madvertisetest/supplemental/display-site-test.json
new file mode 100644
index 00000000000..e111a5d9d71
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/supplemental/display-site-test.json
@@ -0,0 +1,203 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 1,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "instl": 0,
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "pos": 1
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Openrtb-Version": [
+ "2.5"
+ ],
+ "User-Agent": [
+ "test-user-agent"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "body": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 1,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "site": {
+ "page": "test.com",
+ "publisher": {
+ "id": "123456789"
+ }
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "pos": 1
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "burl": "https://burl.com",
+ "adomain": [
+ "madvertise.com"
+ ],
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "seat": "adman"
+ }
+ ],
+ "cur": "EUR"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "burl": "https://burl.com",
+ "adomain": [
+ "madvertise.com"
+ ],
+ "w": 320,
+ "h": 50
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/supplemental/length-zoneid.json b/adapters/madvertise/madvertisetest/supplemental/length-zoneid.json
new file mode 100644
index 00000000000..9d78bc855d9
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/supplemental/length-zoneid.json
@@ -0,0 +1,29 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/22222"
+ }
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "The minLength of zone ID is 7; ImpID=test-imp-id-1",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/supplemental/required-ext.json b/adapters/madvertise/madvertisetest/supplemental/required-ext.json
new file mode 100644
index 00000000000..8989a8dc4e1
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/supplemental/required-ext.json
@@ -0,0 +1,19 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "banner": {
+ "format": [{"w": 300, "h": 250}]
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "unexpected end of JSON input; ImpID=test-imp-id-1",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/madvertise/madvertisetest/supplemental/required-zoneid-.json b/adapters/madvertise/madvertisetest/supplemental/required-zoneid-.json
new file mode 100644
index 00000000000..bfff193d76d
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/supplemental/required-zoneid-.json
@@ -0,0 +1,27 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {}
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "ext.bidder.zoneId not provided; ImpID=test-imp-id-1",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/supplemental/response-204.json b/adapters/madvertise/madvertisetest/supplemental/response-204.json
new file mode 100644
index 00000000000..f0162291d12
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/supplemental/response-204.json
@@ -0,0 +1,189 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 2,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack",
+ "cat": [
+ "IAB6",
+ "IAB9",
+ "IAB10",
+ "IAB12",
+ "IAB14",
+ "IAB20"
+ ]
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "instl": 0,
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "w": 320,
+ "h": 50,
+ "api": [
+ 3,
+ 5,
+ 6
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Openrtb-Version": [
+ "2.5"
+ ],
+ "User-Agent": [
+ "test-user-agent"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "body": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 2,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack",
+ "cat": [
+ "IAB6",
+ "IAB9",
+ "IAB10",
+ "IAB12",
+ "IAB14",
+ "IAB20"
+ ]
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "w": 320,
+ "h": 50,
+ "api": [
+ 3,
+ 5,
+ 6
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/supplemental/response-400.json b/adapters/madvertise/madvertisetest/supplemental/response-400.json
new file mode 100644
index 00000000000..a5af414d7a0
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/supplemental/response-400.json
@@ -0,0 +1,194 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 2,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack",
+ "cat": [
+ "IAB6",
+ "IAB9",
+ "IAB10",
+ "IAB12",
+ "IAB14",
+ "IAB20"
+ ]
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "instl": 0,
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "w": 320,
+ "h": 50,
+ "api": [
+ 3,
+ 5,
+ 6
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Openrtb-Version": [
+ "2.5"
+ ],
+ "User-Agent": [
+ "test-user-agent"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "body": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 2,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack",
+ "cat": [
+ "IAB6",
+ "IAB9",
+ "IAB10",
+ "IAB12",
+ "IAB14",
+ "IAB20"
+ ]
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "w": 320,
+ "h": 50,
+ "api": [
+ 3,
+ 5,
+ 6
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/supplemental/response-500.json b/adapters/madvertise/madvertisetest/supplemental/response-500.json
new file mode 100644
index 00000000000..8a4b0da4323
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/supplemental/response-500.json
@@ -0,0 +1,194 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 2,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack",
+ "cat": [
+ "IAB6",
+ "IAB9",
+ "IAB10",
+ "IAB12",
+ "IAB14",
+ "IAB20"
+ ]
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "instl": 0,
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "w": 320,
+ "h": 50,
+ "api": [
+ 3,
+ 5,
+ 6
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ],
+ "X-Openrtb-Version": [
+ "2.5"
+ ],
+ "User-Agent": [
+ "test-user-agent"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ]
+ },
+ "body": {
+ "id": "test-request-id",
+ "tmax": 500,
+ "at": 2,
+ "cur": [
+ "EUR"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 1
+ }
+ },
+ "user": {
+ "ext": {
+ "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg"
+ }
+ },
+ "app": {
+ "id": "9561622",
+ "bundle": "com.madvertise.bluestack",
+ "name": "FR_BlueStack_App_Android_MNG",
+ "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack",
+ "cat": [
+ "IAB6",
+ "IAB9",
+ "IAB10",
+ "IAB12",
+ "IAB14",
+ "IAB20"
+ ]
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ua": "test-user-agent",
+ "dnt": 0,
+ "lmt": 0,
+ "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111",
+ "make": "Sony",
+ "model": "G8441",
+ "os": "Android",
+ "osv": "9.0",
+ "devicetype": 4,
+ "connectiontype": 6,
+ "js": 1,
+ "carrier": "Free",
+ "geo": {
+ "type": 2,
+ "country": "FRA"
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "secure": 1,
+ "bidfloor": 3,
+ "bidfloorcur": "EUR",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ],
+ "w": 320,
+ "h": 50,
+ "api": [
+ 3,
+ 5,
+ 6
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 500
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/madvertise/madvertisetest/supplemental/unique-zone-id.json b/adapters/madvertise/madvertisetest/supplemental/unique-zone-id.json
new file mode 100644
index 00000000000..68a56652dd7
--- /dev/null
+++ b/adapters/madvertise/madvertisetest/supplemental/unique-zone-id.json
@@ -0,0 +1,45 @@
+{
+ "mockBidRequest": {
+ "id": "test-no-placement-id",
+ "imp": [
+ {
+ "id": "test-imp-id-unique-id-1",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/1111111/banner"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-unique-id-2",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "/2222222/banner"
+ }
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "There must be only one zone ID",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/madvertise/params_test.go b/adapters/madvertise/params_test.go
new file mode 100644
index 00000000000..7c138bf48ac
--- /dev/null
+++ b/adapters/madvertise/params_test.go
@@ -0,0 +1,55 @@
+package madvertise
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/Madvertise.json
+//
+// These also validate the format of the external API: request.imp[i].ext.Madvertise
+
+// TestValidParams makes sure that the Madvertise 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.BidderMadvertise, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected Madvertise params: %s \n Error: %s", validParam, err)
+ }
+ }
+}
+
+// TestInvalidParams makes sure that the Madvertise 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.BidderMadvertise, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"zoneId":"/1111111/banner"}`,
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `[]`,
+ `{}`,
+ `{"zoneId":""}`,
+ `{"zoneId":/1111111}`,
+ `{"zoneId":/1111"}`,
+}
diff --git a/adapters/operaads/operaads.go b/adapters/operaads/operaads.go
new file mode 100644
index 00000000000..890a15ddb5f
--- /dev/null
+++ b/adapters/operaads/operaads.go
@@ -0,0 +1,215 @@
+package operaads
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/prebid/prebid-server/macros"
+ "net/http"
+ "text/template"
+
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+ epTemplate *template.Template
+}
+
+// Builder builds a new instance of the operaads adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+ epTemplate, err := template.New("endpoint").Parse(config.Endpoint)
+ if err != nil {
+ return nil, err
+ }
+ bidder := &adapter{
+ epTemplate: epTemplate,
+ }
+ return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ impCount := len(request.Imp)
+ requestData := make([]*adapters.RequestData, 0, impCount)
+ errs := []error{}
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ err := checkRequest(request)
+ if err != nil {
+ errs = append(errs, err)
+ return nil, errs
+ }
+
+ for _, imp := range request.Imp {
+ requestCopy := *request
+ var bidderExt adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ errs = append(errs, &errortypes.BadInput{
+ Message: err.Error(),
+ })
+ continue
+ }
+
+ var operaadsExt openrtb_ext.ImpExtOperaads
+ if err := json.Unmarshal(bidderExt.Bidder, &operaadsExt); err != nil {
+ errs = append(errs, &errortypes.BadInput{
+ Message: err.Error(),
+ })
+ continue
+ }
+
+ err := convertImpression(&imp)
+ if err != nil {
+ errs = append(errs, &errortypes.BadInput{
+ Message: err.Error(),
+ })
+ continue
+ }
+
+ imp.TagID = operaadsExt.PlacementID
+
+ requestCopy.Imp = []openrtb2.Imp{imp}
+ reqJSON, err := json.Marshal(&requestCopy)
+ if err != nil {
+ errs = append(errs, err)
+ return nil, errs
+ }
+
+ macro := macros.EndpointTemplateParams{PublisherID: operaadsExt.PublisherID, AccountID: operaadsExt.EndpointID}
+ endpoint, err := macros.ResolveMacros(*a.epTemplate, ¯o)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ reqData := &adapters.RequestData{
+ Method: http.MethodPost,
+ Uri: endpoint,
+ Body: reqJSON,
+ Headers: headers,
+ }
+ requestData = append(requestData, reqData)
+ }
+ return requestData, errs
+}
+
+func checkRequest(request *openrtb2.BidRequest) error {
+ if request.Device == nil || len(request.Device.OS) == 0 {
+ return &errortypes.BadInput{
+ Message: "Impression is missing device OS information",
+ }
+ }
+
+ return nil
+}
+
+func convertImpression(imp *openrtb2.Imp) error {
+ if imp.Banner != nil {
+ bannerCopy, err := convertBanner(imp.Banner)
+ if err != nil {
+ return err
+ }
+ imp.Banner = bannerCopy
+ }
+ if imp.Native != nil && imp.Native.Request != "" {
+ v := make(map[string]interface{})
+ err := json.Unmarshal([]byte(imp.Native.Request), &v)
+ if err != nil {
+ return err
+ }
+ _, ok := v["native"]
+ if !ok {
+ body, err := json.Marshal(struct {
+ Native interface{} `json:"native"`
+ }{
+ Native: v,
+ })
+ if err != nil {
+ return err
+ }
+ native := *imp.Native
+ native.Request = string(body)
+ imp.Native = &native
+ }
+ }
+ return nil
+}
+
+// make sure that banner has openrtb 2.3-compatible size information
+func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) {
+ if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 {
+ if len(banner.Format) > 0 {
+ f := banner.Format[0]
+
+ bannerCopy := *banner
+
+ bannerCopy.W = openrtb2.Int64Ptr(f.W)
+ bannerCopy.H = openrtb2.Int64Ptr(f.H)
+
+ return &bannerCopy, nil
+ } else {
+ return nil, &errortypes.BadInput{
+ Message: "Size information missing for banner",
+ }
+ }
+ }
+ return banner, nil
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.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 parsedResponse openrtb2.BidResponse
+ if err := json.Unmarshal(response.Body, &parsedResponse); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: err.Error(),
+ }}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+
+ for _, sb := range parsedResponse.SeatBid {
+ for i := 0; i < len(sb.Bid); i++ {
+ bid := sb.Bid[i]
+ if bid.Price != 0 {
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp),
+ })
+ }
+ }
+ }
+ return bidResponse, nil
+}
+
+func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
+ mediaType := openrtb_ext.BidTypeBanner
+ for _, imp := range imps {
+ if imp.ID == impId {
+ if imp.Video != nil {
+ mediaType = openrtb_ext.BidTypeVideo
+ } else if imp.Native != nil {
+ mediaType = openrtb_ext.BidTypeNative
+ }
+ return mediaType
+ }
+ }
+ return mediaType
+}
diff --git a/adapters/operaads/operaads_test.go b/adapters/operaads/operaads_test.go
new file mode 100644
index 00000000000..eb4280b68e9
--- /dev/null
+++ b/adapters/operaads/operaads_test.go
@@ -0,0 +1,20 @@
+package operaads
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderOperaads, config.Adapter{
+ Endpoint: "http://example.com/operaads/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "operaadstest", bidder)
+}
diff --git a/adapters/operaads/operaadstest/exemplary/native.json b/adapters/operaads/operaadstest/exemplary/native.json
new file mode 100644
index 00000000000..4491bd150e4
--- /dev/null
+++ b/adapters/operaads/operaadstest/exemplary/native.json
@@ -0,0 +1,137 @@
+{
+ "mockBidRequest":{
+ "id":"some-req-id",
+ "imp":[
+ {
+ "id":"some-imp-id",
+ "native":{
+ "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
+ "ver":"1.1"
+ },
+ "ext":{
+ "bidder":{
+ "placementId":"s123456",
+ "endpointId":"ep19978",
+ "publisherId":"pub123"
+ }
+ }
+ }
+ ],
+ "site":{
+ "domain":"example.com",
+ "page":"example.com"
+ },
+ "device":{
+ "ip":"152.193.6.74",
+ "os":"android"
+ },
+ "user":{
+ "id":"db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid":"8299345306627569435"
+ },
+ "tmax":500
+ },
+ "httpcalls":[
+ {
+ "expectedRequest":{
+ "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978",
+ "body":{
+ "id":"some-req-id",
+ "imp":[
+ {
+ "id":"some-imp-id",
+ "tagid":"s123456",
+ "ext":{
+ "bidder":{
+ "placementId":"s123456",
+ "endpointId":"ep19978",
+ "publisherId":"pub123"
+ }
+ },
+ "native":{
+ "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
+ "ver":"1.1"
+ }
+ }
+ ],
+ "site":{
+ "domain":"example.com",
+ "page":"example.com"
+ },
+ "device":{
+ "ip":"152.193.6.74",
+ "os":"android"
+ },
+ "user":{
+ "id":"db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid":"8299345306627569435"
+ },
+ "tmax":500
+ }
+ },
+ "mockResponse":{
+ "status":200,
+ "body":{
+ "id":"some-req-id",
+ "seatbid":[
+ {
+ "bid":[
+ {
+ "id":"928185755156387460",
+ "impid":"some-imp-id",
+ "price":1,
+ "adid":"69595837",
+ "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}",
+ "adomain":[
+ "example.com"
+ ],
+ "iurl":"http://example.com/cr?id=69595837",
+ "cid":"958",
+ "crid":"69595837",
+ "cat":[
+ "IAB3-1"
+ ],
+ "ext":{
+
+ }
+ }
+ ],
+ "seat":"958"
+ }
+ ],
+ "bidid":"8141327771600527856",
+ "cur":"USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses":[
+ {
+ "currency":"USD",
+ "bids":[
+ {
+ "bid":{
+ "id":"928185755156387460",
+ "impid":"some-imp-id",
+ "price":1,
+ "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}",
+ "adid":"69595837",
+ "adomain":[
+ "example.com"
+ ],
+ "iurl":"http://example.com/cr?id=69595837",
+ "cid":"958",
+ "crid":"69595837",
+ "cat":[
+ "IAB3-1"
+ ],
+ "ext":{
+
+ }
+ },
+ "type":"native"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/operaads/operaadstest/exemplary/simple-banner.json b/adapters/operaads/operaadstest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..53b19c82c2a
--- /dev/null
+++ b/adapters/operaads/operaadstest/exemplary/simple-banner.json
@@ -0,0 +1,156 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "domain": "good.site",
+ "page": "https://good.site/url",
+ "publisher": {
+ "id": "test-publisher-id"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "placementId": "s17890",
+ "endpointId": "ep19979",
+ "publisherId": "pub456"
+ }
+ }
+ }
+ ],
+ "device": {
+ "os": "android",
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "h": 700,
+ "w": 375
+ },
+ "at": 1,
+ "tmax": 200,
+ "test": 1,
+ "source": {
+ "tid": "283746293874293"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com/operaads/ortb/v2/pub456?ep=ep19979",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "domain": "good.site",
+ "page": "https://good.site/url",
+ "publisher": {
+ "id": "test-publisher-id"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "tagid": "s17890",
+ "banner": {
+ "h": 250,
+ "w": 300,
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "placementId": "s17890",
+ "endpointId": "ep19979",
+ "publisherId": "pub456"
+ }
+ }
+ }
+ ],
+ "device": {
+ "os": "android",
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "h": 700,
+ "w": 375
+ },
+ "at": 1,
+ "tmax": 200,
+ "test": 1,
+ "source": {
+ "tid": "283746293874293"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "1519967420713_259406708_583019428",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "impid": "1",
+ "auid": 46,
+ "id": "1",
+ "h": 250,
+ "adomain": [
+ "goodadvertiser.com"
+ ],
+ "crid": "11_222222",
+ "w": 300
+ }
+ ],
+ "seat": "51"
+ }
+ ],
+ "bidid":"8141327771600527856",
+ "cur":"USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency":"USD",
+ "bids": [
+ {
+ "bid": {
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "impid": "1",
+ "id": "1",
+ "h": 250,
+ "adomain": [
+ "goodadvertiser.com"
+ ],
+ "crid": "11_222222",
+ "w": 300
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/operaads/operaadstest/exemplary/video.json b/adapters/operaads/operaadstest/exemplary/video.json
new file mode 100644
index 00000000000..a76bbe5ccf8
--- /dev/null
+++ b/adapters/operaads/operaadstest/exemplary/video.json
@@ -0,0 +1,173 @@
+{
+ "mockBidRequest": {
+ "id": "test-video-request",
+ "imp": [
+ {
+ "id": "test-video-imp",
+ "video": {
+ "w": 640,
+ "h": 480,
+ "mimes": [
+ "video/mp4",
+ "video/x-flv"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "startdelay": 5,
+ "playbackmethod": [
+ 1,
+ 3
+ ],
+ "api": [
+ 1,
+ 2
+ ],
+ "protocols": [
+ 2,
+ 3
+ ],
+ "battr": [
+ 13,
+ 14
+ ],
+ "linearity": 1,
+ "placement": 2,
+ "minbitrate": 10,
+ "maxbitrate": 10
+ },
+ "ext": {
+ "bidder": {
+ "placementId":"s00000",
+ "endpointId":"ep00000",
+ "publisherId":"pub00000"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
+ "os": "android"
+ },
+ "site": {
+ "id": "siteID",
+ "publisher": {
+ "id": "1234"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com/operaads/ortb/v2/pub00000?ep=ep00000",
+ "body": {
+ "id": "test-video-request",
+ "imp": [
+ {
+ "id": "test-video-imp",
+ "tagid": "s00000",
+ "video": {
+ "w": 640,
+ "h": 480,
+ "mimes": [
+ "video/mp4",
+ "video/x-flv"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "startdelay": 5,
+ "playbackmethod": [
+ 1,
+ 3
+ ],
+ "api": [
+ 1,
+ 2
+ ],
+ "protocols": [
+ 2,
+ 3
+ ],
+ "battr": [
+ 13,
+ 14
+ ],
+ "linearity": 1,
+ "placement": 2,
+ "minbitrate": 10,
+ "maxbitrate": 10
+ },
+ "ext": {
+ "bidder": {
+ "placementId":"s00000",
+ "endpointId":"ep00000",
+ "publisherId":"pub00000"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
+ "os": "android"
+ },
+ "site": {
+ "id": "siteID",
+ "publisher": {
+ "id": "1234"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-video-request",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-video-imp",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": [
+ "example.com"
+ ],
+ "crid": "29681110",
+ "h": 250,
+ "w": 300
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-video-imp",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": [
+ "example.com"
+ ],
+ "crid": "29681110",
+ "w": 300,
+ "h": 250
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/operaads/operaadstest/supplemental/badrequest.json b/adapters/operaads/operaadstest/supplemental/badrequest.json
new file mode 100644
index 00000000000..ff3fe071c4a
--- /dev/null
+++ b/adapters/operaads/operaadstest/supplemental/badrequest.json
@@ -0,0 +1,84 @@
+{
+ "mockBidRequest":{
+ "id":"some-req-id",
+ "imp":[
+ {
+ "id":"some-imp-id",
+ "native":{
+ "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
+ "ver":"1.1"
+ },
+ "ext":{
+ "bidder":{
+ "placementId":"s123456",
+ "endpointId":"ep19978",
+ "publisherId":"pub123"
+ }
+ }
+ }
+ ],
+ "site":{
+ "domain":"example.com",
+ "page":"example.com"
+ },
+ "device":{
+ "ip":"152.193.6.74",
+ "os":"android"
+ },
+ "user":{
+ "id":"db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid":"8299345306627569435"
+ },
+ "tmax":500
+ },
+ "httpcalls":[
+ {
+ "expectedRequest":{
+ "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978",
+ "body":{
+ "id":"some-req-id",
+ "imp":[
+ {
+ "id":"some-imp-id",
+ "tagid":"s123456",
+ "ext":{
+ "bidder":{
+ "placementId":"s123456",
+ "endpointId":"ep19978",
+ "publisherId":"pub123"
+ }
+ },
+ "native":{
+ "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
+ "ver":"1.1"
+ }
+ }
+ ],
+ "site":{
+ "domain":"example.com",
+ "page":"example.com"
+ },
+ "device":{
+ "ip":"152.193.6.74",
+ "os":"android"
+ },
+ "user":{
+ "id":"db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid":"8299345306627569435"
+ },
+ "tmax":500
+ }
+ },
+ "mockResponse":{
+ "status":400,
+ "body":{}
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/operaads/operaadstest/supplemental/banner-size-miss.json b/adapters/operaads/operaadstest/supplemental/banner-size-miss.json
new file mode 100644
index 00000000000..70ac350c2f0
--- /dev/null
+++ b/adapters/operaads/operaadstest/supplemental/banner-size-miss.json
@@ -0,0 +1,50 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "domain": "good.site",
+ "page": "https://good.site/url",
+ "publisher": {
+ "id": "test-publisher-id"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": []
+ },
+ "secure": 1,
+ "ext": {
+ "bidder": {
+ "placementId": "s17890",
+ "endpointId": "ep19979",
+ "publisherId": "pub456"
+ }
+ }
+ }
+ ],
+ "device": {
+ "os": "android",
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "h": 700,
+ "w": 375
+ },
+ "at": 1,
+ "tmax": 200,
+ "test": 1,
+ "source": {
+ "tid": "283746293874293"
+ }
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Size information missing for banner",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/operaads/operaadstest/supplemental/miss-native.json b/adapters/operaads/operaadstest/supplemental/miss-native.json
new file mode 100644
index 00000000000..918bbc4ded5
--- /dev/null
+++ b/adapters/operaads/operaadstest/supplemental/miss-native.json
@@ -0,0 +1,137 @@
+{
+ "mockBidRequest":{
+ "id":"some-req-id",
+ "imp":[
+ {
+ "id":"some-imp-id",
+ "native":{
+ "request":"{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}",
+ "ver":"1.1"
+ },
+ "ext":{
+ "bidder":{
+ "placementId":"s123456",
+ "endpointId":"ep19978",
+ "publisherId":"pub123"
+ }
+ }
+ }
+ ],
+ "site":{
+ "domain":"example.com",
+ "page":"example.com"
+ },
+ "device":{
+ "ip":"152.193.6.74",
+ "os":"android"
+ },
+ "user":{
+ "id":"db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid":"8299345306627569435"
+ },
+ "tmax":500
+ },
+ "httpcalls":[
+ {
+ "expectedRequest":{
+ "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978",
+ "body":{
+ "id":"some-req-id",
+ "imp":[
+ {
+ "id":"some-imp-id",
+ "tagid":"s123456",
+ "ext":{
+ "bidder":{
+ "placementId":"s123456",
+ "endpointId":"ep19978",
+ "publisherId":"pub123"
+ }
+ },
+ "native":{
+ "request":"{\"native\":{\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"img\":{\"hmin\":194,\"type\":3,\"wmin\":344},\"required\":1},{\"id\":3,\"img\":{\"h\":128,\"hmin\":80,\"type\":1,\"w\":128,\"wmin\":80},\"required\":1},{\"data\":{\"len\":90,\"type\":2},\"id\":4,\"required\":1},{\"data\":{\"len\":15,\"type\":12},\"id\":6}],\"layout\":3,\"ver\":\"1.1\"}}",
+ "ver":"1.1"
+ }
+ }
+ ],
+ "site":{
+ "domain":"example.com",
+ "page":"example.com"
+ },
+ "device":{
+ "ip":"152.193.6.74",
+ "os":"android"
+ },
+ "user":{
+ "id":"db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid":"8299345306627569435"
+ },
+ "tmax":500
+ }
+ },
+ "mockResponse":{
+ "status":200,
+ "body":{
+ "id":"some-req-id",
+ "seatbid":[
+ {
+ "bid":[
+ {
+ "id":"928185755156387460",
+ "impid":"some-imp-id",
+ "price":1,
+ "adid":"69595837",
+ "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}",
+ "adomain":[
+ "example.com"
+ ],
+ "iurl":"http://example.com/cr?id=69595837",
+ "cid":"958",
+ "crid":"69595837",
+ "cat":[
+ "IAB3-1"
+ ],
+ "ext":{
+
+ }
+ }
+ ],
+ "seat":"958"
+ }
+ ],
+ "bidid":"8141327771600527856",
+ "cur":"USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses":[
+ {
+ "currency":"USD",
+ "bids":[
+ {
+ "bid":{
+ "id":"928185755156387460",
+ "impid":"some-imp-id",
+ "price":1,
+ "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}",
+ "adid":"69595837",
+ "adomain":[
+ "example.com"
+ ],
+ "iurl":"http://example.com/cr?id=69595837",
+ "cid":"958",
+ "crid":"69595837",
+ "cat":[
+ "IAB3-1"
+ ],
+ "ext":{
+
+ }
+ },
+ "type":"native"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/operaads/operaadstest/supplemental/missing-device.json b/adapters/operaads/operaadstest/supplemental/missing-device.json
new file mode 100644
index 00000000000..3ba01d81632
--- /dev/null
+++ b/adapters/operaads/operaadstest/supplemental/missing-device.json
@@ -0,0 +1,33 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {}
+ }
+ }
+ ],
+ "site": {
+ "id": "siteID",
+ "publisher": {
+ "id": "1234"
+ }
+ }
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Impression is missing device OS information",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/operaads/operaadstest/supplemental/nocontent.json b/adapters/operaads/operaadstest/supplemental/nocontent.json
new file mode 100644
index 00000000000..d6baf35d4a6
--- /dev/null
+++ b/adapters/operaads/operaadstest/supplemental/nocontent.json
@@ -0,0 +1,82 @@
+{
+ "mockBidRequest": {
+ "id": "some-req-id",
+ "imp": [
+ {
+ "id": "some-imp-id",
+ "native": {
+ "request": "{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "placementId": "s123456",
+ "endpointId": "ep19978",
+ "publisherId": "pub123"
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "example.com",
+ "page": "example.com"
+ },
+ "device": {
+ "ip": "152.193.6.74",
+ "os": "android"
+ },
+ "user": {
+ "id": "db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid": "8299345306627569435"
+ },
+ "tmax": 500
+ },
+ "httpcalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com/operaads/ortb/v2/pub123?ep=ep19978",
+ "body": {
+ "id": "some-req-id",
+ "imp": [
+ {
+ "id": "some-imp-id",
+ "tagid": "s123456",
+ "ext": {
+ "bidder": {
+ "placementId": "s123456",
+ "endpointId": "ep19978",
+ "publisherId": "pub123"
+ }
+ },
+ "native": {
+ "request": "{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
+ "ver": "1.1"
+ }
+ }
+ ],
+ "site": {
+ "domain": "example.com",
+ "page": "example.com"
+ },
+ "device": {
+ "ip": "152.193.6.74",
+ "os": "android"
+ },
+ "user": {
+ "id": "db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid": "8299345306627569435"
+ },
+ "tmax": 500
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ ],
+ "expectedMakeBidsErrors": [
+ ]
+}
\ No newline at end of file
diff --git a/adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json b/adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json
new file mode 100644
index 00000000000..a6c8c5052ae
--- /dev/null
+++ b/adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json
@@ -0,0 +1,84 @@
+{
+ "mockBidRequest":{
+ "id":"some-req-id",
+ "imp":[
+ {
+ "id":"some-imp-id",
+ "native":{
+ "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
+ "ver":"1.1"
+ },
+ "ext":{
+ "bidder":{
+ "placementId":"s123456",
+ "endpointId":"ep19978",
+ "publisherId":"pub123"
+ }
+ }
+ }
+ ],
+ "site":{
+ "domain":"example.com",
+ "page":"example.com"
+ },
+ "device":{
+ "ip":"152.193.6.74",
+ "os":"android"
+ },
+ "user":{
+ "id":"db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid":"8299345306627569435"
+ },
+ "tmax":500
+ },
+ "httpcalls":[
+ {
+ "expectedRequest":{
+ "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978",
+ "body":{
+ "id":"some-req-id",
+ "imp":[
+ {
+ "id":"some-imp-id",
+ "tagid":"s123456",
+ "ext":{
+ "bidder":{
+ "placementId":"s123456",
+ "endpointId":"ep19978",
+ "publisherId":"pub123"
+ }
+ },
+ "native":{
+ "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
+ "ver":"1.1"
+ }
+ }
+ ],
+ "site":{
+ "domain":"example.com",
+ "page":"example.com"
+ },
+ "device":{
+ "ip":"152.193.6.74",
+ "os":"android"
+ },
+ "user":{
+ "id":"db089de9-a62e-4861-a881-0ff15e052516",
+ "buyeruid":"8299345306627569435"
+ },
+ "tmax":500
+ }
+ },
+ "mockResponse":{
+ "status":205,
+ "body":{}
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 205. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/operaads/params_test.go b/adapters/operaads/params_test.go
new file mode 100644
index 00000000000..e998127b001
--- /dev/null
+++ b/adapters/operaads/params_test.go
@@ -0,0 +1,54 @@
+package operaads
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/operaads.json
+//
+// These also validate the format of the external API: request.imp[i].ext.operaads
+
+// TestValidParams makes sure that the operaads 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.BidderOperaads, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected operaads params: %s", validParam)
+ }
+ }
+}
+
+// TestInvalidParams makes sure that the operaads 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.BidderOpenx, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"placementId": "s123", "endpointId": "ep12345", "publisherId": "pub12345"}`,
+}
+
+var invalidParams = []string{
+ `{"placementId": "s123"}`,
+ `{"endpointId": "ep12345"}`,
+ `{"publisherId": "pub12345"}`,
+ `{"placementId": "s123", "endpointId": "ep12345"}`,
+ `{"placementId": "s123", "publisherId": "pub12345"}`,
+ `{"endpointId": "ep12345", "publisherId": "pub12345"}`,
+ `{"placementId": "", "endpointId": "", "publisherId": ""}`,
+}
diff --git a/adapters/operaads/usersync.go b/adapters/operaads/usersync.go
new file mode 100644
index 00000000000..aae6fb600e3
--- /dev/null
+++ b/adapters/operaads/usersync.go
@@ -0,0 +1,11 @@
+package operaads
+
+import (
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/usersync"
+ "text/template"
+)
+
+func NewOperaadsSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("operaads", temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/operaads/usersync_test.go b/adapters/operaads/usersync_test.go
new file mode 100644
index 00000000000..e9b402ac465
--- /dev/null
+++ b/adapters/operaads/usersync_test.go
@@ -0,0 +1,33 @@
+package operaads
+
+import (
+ "github.com/prebid/prebid-server/privacy/ccpa"
+ "github.com/prebid/prebid-server/privacy/gdpr"
+ "testing"
+ "text/template"
+
+ "github.com/prebid/prebid-server/privacy"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestOperaadsSyncer(t *testing.T) {
+ syncURL := "https://t-odx.op-mobile.opera.com/pbs/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r=localhost%2Fsetuid%3Fbidder%3Doperaads%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewOperaadsSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "A",
+ Consent: "B",
+ },
+ CCPA: ccpa.Policy{
+ Consent: "C",
+ }})
+
+ assert.NoError(t, err)
+ assert.Equal(t, "https://t-odx.op-mobile.opera.com/pbs/sync?gdpr=A&gdpr_consent=B&r=localhost%2Fsetuid%3Fbidder%3Doperaads%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUID%7D", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go
index 282a6d53aa0..6b121cb4732 100644
--- a/adapters/outbrain/outbrain.go
+++ b/adapters/outbrain/outbrain.go
@@ -43,8 +43,10 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte
errs = append(errs, err)
continue
}
- imp.TagID = outbrainExt.TagId
- reqCopy.Imp[i] = imp
+ if outbrainExt.TagId != "" {
+ imp.TagID = outbrainExt.TagId
+ reqCopy.Imp[i] = imp
+ }
}
publisher := &openrtb2.Publisher{
diff --git a/adapters/outbrain/outbraintest/supplemental/general_params.json b/adapters/outbrain/outbraintest/supplemental/general_params.json
new file mode 100644
index 00000000000..b2a547c8b4e
--- /dev/null
+++ b/adapters/outbrain/outbraintest/supplemental/general_params.json
@@ -0,0 +1,139 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "tag-id",
+ "ext": {
+ "bidder": {
+ "publisher": {
+ "id": "publisher-id"
+ }
+ }
+ }
+ }
+ ],
+ "bcat": ["bad-category"],
+ "badv": ["bad-advertiser"],
+ "site": {
+ "page": "http://example.com"
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
+ "h": 500,
+ "w": 1000
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com/bid",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "tag-id",
+ "ext": {
+ "bidder": {
+ "publisher": {
+ "id": "publisher-id"
+ }
+ }
+ }
+ }
+ ],
+ "bcat": ["bad-category"],
+ "badv": ["bad-advertiser"],
+ "site": {
+ "page": "http://example.com",
+ "publisher": {
+ "id": "publisher-id"
+ }
+ },
+ "device": {
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
+ "h": 500,
+ "w": 1000
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5095a742-1c27-402b-ab6f-66b1bd53383b",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-request-id",
+ "impid": "test-imp-id",
+ "price": 1000,
+ "nurl": "http://example.com/win/1000",
+ "adm": "ad
",
+ "adomain": [
+ "example.com"
+ ],
+ "cid": "test-cid",
+ "crid": "test-crid",
+ "cat": [
+ "IAB13-4"
+ ],
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "seat": "acc-1876"
+ }
+ ],
+ "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-request-id",
+ "impid": "test-imp-id",
+ "price": 1000,
+ "nurl": "http://example.com/win/1000",
+ "adm": "ad
",
+ "adomain": [
+ "example.com"
+ ],
+ "cid": "test-cid",
+ "crid": "test-crid",
+ "cat": [
+ "IAB13-4"
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/outbrain/outbraintest/supplemental/optional_params.json b/adapters/outbrain/outbraintest/supplemental/optional_params.json
index a69ceaa0c85..d75875e0e70 100644
--- a/adapters/outbrain/outbraintest/supplemental/optional_params.json
+++ b/adapters/outbrain/outbraintest/supplemental/optional_params.json
@@ -12,6 +12,7 @@
}
]
},
+ "tagid": "should-be-overwritten-tagid",
"ext": {
"bidder": {
"publisher": {
@@ -26,6 +27,8 @@
}
}
],
+ "bcat": ["should-be-overwritten-bcat"],
+ "badv": ["should-be-overwritten-badv"],
"site": {
"page": "http://example.com"
},
diff --git a/adapters/pangle/pangle.go b/adapters/pangle/pangle.go
index a4694c71559..da86a904e5c 100644
--- a/adapters/pangle/pangle.go
+++ b/adapters/pangle/pangle.go
@@ -16,9 +16,16 @@ type adapter struct {
Endpoint string
}
+type NetworkIDs struct {
+ AppID string `json:"appid,omitempty"`
+ PlacementID string `json:"placementid,omitempty"`
+}
+
type wrappedExtImpBidder struct {
*adapters.ExtImpBidder
- AdType int `json:"adtype,omitempty"`
+ AdType int `json:"adtype,omitempty"`
+ IsPrebid bool `json:"is_prebid,omitempty"`
+ NetworkIDs *NetworkIDs `json:"networkids,omitempty"`
}
type pangleBidExt struct {
@@ -78,23 +85,34 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte
errs = append(errs, fmt.Errorf("failed unmarshalling imp ext (err)%s", err.Error()))
continue
}
+ // get token & networkIDs
+ var bidderImpExt openrtb_ext.ImpExtPangle
+ if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil {
+ errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error()))
+ continue
+ }
// detect and fill adtype
- if adType := getAdType(imp, &impExt); adType == -1 {
+ adType := getAdType(imp, &impExt)
+ if adType == -1 {
errs = append(errs, &errortypes.BadInput{Message: "not a supported adtype"})
continue
- } else {
- impExt.AdType = adType
- if newImpExt, err := json.Marshal(impExt); err == nil {
- imp.Ext = newImpExt
- } else {
- errs = append(errs, fmt.Errorf("failed re-marshalling imp ext with adtype"))
- continue
+ }
+ // remarshal imp.ext
+ impExt.AdType = adType
+ impExt.IsPrebid = true
+ if len(bidderImpExt.AppID) > 0 && len(bidderImpExt.PlacementID) > 0 {
+ impExt.NetworkIDs = &NetworkIDs{
+ AppID: bidderImpExt.AppID,
+ PlacementID: bidderImpExt.PlacementID,
}
+ } else if len(bidderImpExt.AppID) > 0 || len(bidderImpExt.PlacementID) > 0 {
+ errs = append(errs, &errortypes.BadInput{Message: "only one of appid or placementid is provided"})
+ continue
}
- // for setting token
- var bidderImpExt openrtb_ext.ImpExtPangle
- if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil {
- errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error()))
+ if newImpExt, err := json.Marshal(impExt); err == nil {
+ imp.Ext = newImpExt
+ } else {
+ errs = append(errs, fmt.Errorf("failed re-marshalling imp ext"))
continue
}
diff --git a/adapters/pangle/pangletest/exemplary/app_banner.json b/adapters/pangle/pangletest/exemplary/app_banner.json
index 3fa410e5b7f..95694eb3552 100644
--- a/adapters/pangle/pangletest/exemplary/app_banner.json
+++ b/adapters/pangle/pangletest/exemplary/app_banner.json
@@ -62,6 +62,7 @@
"bidder": {
"token": "123"
},
+ "is_prebid": true,
"prebid": null
}
}
diff --git a/adapters/pangle/pangletest/exemplary/app_banner_instl.json b/adapters/pangle/pangletest/exemplary/app_banner_instl.json
index 585d155a057..1f11229c01f 100644
--- a/adapters/pangle/pangletest/exemplary/app_banner_instl.json
+++ b/adapters/pangle/pangletest/exemplary/app_banner_instl.json
@@ -64,6 +64,7 @@
"bidder": {
"token": "123"
},
+ "is_prebid": true,
"prebid": null
}
}
diff --git a/adapters/pangle/pangletest/exemplary/app_native.json b/adapters/pangle/pangletest/exemplary/app_native.json
index 2502baa4f9f..44d3f98d0b4 100644
--- a/adapters/pangle/pangletest/exemplary/app_native.json
+++ b/adapters/pangle/pangletest/exemplary/app_native.json
@@ -52,6 +52,7 @@
"bidder": {
"token": "123"
},
+ "is_prebid": true,
"prebid": null
}
}
diff --git a/adapters/pangle/pangletest/exemplary/app_video_instl.json b/adapters/pangle/pangletest/exemplary/app_video_instl.json
index d5af392fd91..778baca5996 100644
--- a/adapters/pangle/pangletest/exemplary/app_video_instl.json
+++ b/adapters/pangle/pangletest/exemplary/app_video_instl.json
@@ -76,6 +76,7 @@
"bidder": {
"token": "123"
},
+ "is_prebid": true,
"prebid": null
}
}
diff --git a/adapters/pangle/pangletest/exemplary/app_video_rewarded.json b/adapters/pangle/pangletest/exemplary/app_video_rewarded.json
index 2dbf08b944e..d23dff1e516 100644
--- a/adapters/pangle/pangletest/exemplary/app_video_rewarded.json
+++ b/adapters/pangle/pangletest/exemplary/app_video_rewarded.json
@@ -79,6 +79,7 @@
"is_rewarded_inventory": 1,
"storedrequest": null
},
+ "is_prebid": true,
"bidder": {
"token": "123"
}
diff --git a/adapters/pangle/pangletest/params/race/banner.json b/adapters/pangle/pangletest/params/race/banner.json
index afd1684b04e..4a9da04cf8e 100644
--- a/adapters/pangle/pangletest/params/race/banner.json
+++ b/adapters/pangle/pangletest/params/race/banner.json
@@ -1,3 +1,5 @@
{
- "token": "123"
+ "token": "123",
+ "appid": "123",
+ "placementid": "123"
}
\ No newline at end of file
diff --git a/adapters/pangle/pangletest/params/race/native.json b/adapters/pangle/pangletest/params/race/native.json
index afd1684b04e..4a9da04cf8e 100644
--- a/adapters/pangle/pangletest/params/race/native.json
+++ b/adapters/pangle/pangletest/params/race/native.json
@@ -1,3 +1,5 @@
{
- "token": "123"
+ "token": "123",
+ "appid": "123",
+ "placementid": "123"
}
\ No newline at end of file
diff --git a/adapters/pangle/pangletest/params/race/video.json b/adapters/pangle/pangletest/params/race/video.json
index afd1684b04e..4a9da04cf8e 100644
--- a/adapters/pangle/pangletest/params/race/video.json
+++ b/adapters/pangle/pangletest/params/race/video.json
@@ -1,3 +1,5 @@
{
- "token": "123"
+ "token": "123",
+ "appid": "123",
+ "placementid": "123"
}
\ No newline at end of file
diff --git a/adapters/pangle/pangletest/supplemental/appid_placementid_check.json b/adapters/pangle/pangletest/supplemental/appid_placementid_check.json
new file mode 100644
index 00000000000..4808cb2bbb9
--- /dev/null
+++ b/adapters/pangle/pangletest/supplemental/appid_placementid_check.json
@@ -0,0 +1,155 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid"
+ },
+ "device": {
+ "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 1024,
+ "h": 576,
+ "ext": {
+ "foo": "bar"
+ }
+ },
+ "ext": {
+ "prebid": {
+ "is_rewarded_inventory": 1
+ },
+ "bidder": {
+ "token": "123",
+ "appid": "123456",
+ "placementid": "78910"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://pangle.io/api/get_ads",
+ "headers": {
+ "Content-Type": [
+ "application/json"
+ ],
+ "TOKEN": [
+ "123"
+ ]
+ },
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid"
+ },
+ "device": {
+ "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 1024,
+ "h": 576,
+ "ext": {
+ "foo": "bar"
+ }
+ },
+ "ext": {
+ "adtype": 7,
+ "prebid": {
+ "bidder": null,
+ "is_rewarded_inventory": 1,
+ "storedrequest": null
+ },
+ "is_prebid": true,
+ "networkids": {
+ "appid": "123456",
+ "placementid": "78910"
+ },
+ "bidder": {
+ "token": "123",
+ "appid": "123456",
+ "placementid": "78910"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "seat-id",
+ "bid": [
+ {
+ "id": "1",
+ "impid": "test-imp-id",
+ "adid": "11110126",
+ "price": 0.500000,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "pangle": {
+ "adtype": 7
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "1",
+ "impid": "test-imp-id",
+ "adid": "11110126",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "pangle": {
+ "adtype": 7
+ }
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/pangle/pangletest/supplemental/missing_appid_or_placementid.json b/adapters/pangle/pangletest/supplemental/missing_appid_or_placementid.json
new file mode 100644
index 00000000000..929b3c6ea88
--- /dev/null
+++ b/adapters/pangle/pangletest/supplemental/missing_appid_or_placementid.json
@@ -0,0 +1,47 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid"
+ },
+ "device": {
+ "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 1024,
+ "h": 576,
+ "ext": {
+ "foo": "bar"
+ }
+ },
+ "ext": {
+ "prebid": {
+ "is_rewarded_inventory": 1
+ },
+ "bidder": {
+ "token": "123",
+ "placementid": "78910"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [],
+ "expectedBidResponses": [],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "only one of appid or placementid is provided",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/pangle/pangletest/supplemental/pangle_ext_check.json b/adapters/pangle/pangletest/supplemental/pangle_ext_check.json
index c9dc5c28c6a..e80c199a180 100644
--- a/adapters/pangle/pangletest/supplemental/pangle_ext_check.json
+++ b/adapters/pangle/pangletest/supplemental/pangle_ext_check.json
@@ -62,6 +62,7 @@
"bidder": {
"token": "123"
},
+ "is_prebid": true,
"prebid": null
}
}
diff --git a/adapters/pangle/pangletest/supplemental/response_code_204.json b/adapters/pangle/pangletest/supplemental/response_code_204.json
index 16c13bdf18f..a1e940a027e 100644
--- a/adapters/pangle/pangletest/supplemental/response_code_204.json
+++ b/adapters/pangle/pangletest/supplemental/response_code_204.json
@@ -62,6 +62,7 @@
"bidder": {
"token": "123"
},
+ "is_prebid": true,
"prebid": null
}
}
diff --git a/adapters/pangle/pangletest/supplemental/response_code_400.json b/adapters/pangle/pangletest/supplemental/response_code_400.json
index 0a5810325e2..841dba8d009 100644
--- a/adapters/pangle/pangletest/supplemental/response_code_400.json
+++ b/adapters/pangle/pangletest/supplemental/response_code_400.json
@@ -62,6 +62,7 @@
"bidder": {
"token": "123"
},
+ "is_prebid": true,
"prebid": null
}
}
diff --git a/adapters/pangle/pangletest/supplemental/response_code_non_200.json b/adapters/pangle/pangletest/supplemental/response_code_non_200.json
index 0d1447db1fe..b87e0f51c9e 100644
--- a/adapters/pangle/pangletest/supplemental/response_code_non_200.json
+++ b/adapters/pangle/pangletest/supplemental/response_code_non_200.json
@@ -62,6 +62,7 @@
"bidder": {
"token": "123"
},
+ "is_prebid": true,
"prebid": null
}
}
diff --git a/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json b/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json
index 451c0ed1909..613694a98b1 100644
--- a/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json
+++ b/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json
@@ -62,6 +62,7 @@
"bidder": {
"token": "123"
},
+ "is_prebid": true,
"prebid": null
}
}
diff --git a/adapters/pangle/param_test.go b/adapters/pangle/param_test.go
index 7b037bd52d6..5e1d30b3c7b 100644
--- a/adapters/pangle/param_test.go
+++ b/adapters/pangle/param_test.go
@@ -9,6 +9,7 @@ import (
var validParams = []string{
`{"token": "SomeAccessToken"}`,
+ `{"token": "SomeAccessToken", "appid": "12345", "placementid": "12345"}`,
}
var invalidParams = []string{
@@ -16,6 +17,12 @@ var invalidParams = []string{
`{"token": 42}`,
`{"token": null}`,
`{}`,
+ // appid & placementid
+ `{"appid": "12345", "placementid": "12345"}`,
+ `{"token": "SomeAccessToken", "appid": "12345"}`,
+ `{"token": "SomeAccessToken", "placementid": "12345"}`,
+ `{"token": "SomeAccessToken", "appid": 12345, "placementid": 12345}`,
+ `{"token": "SomeAccessToken", "appid": null, "placementid": null}`,
}
func TestValidParams(t *testing.T) {
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index d787846c76b..f88a35bef8d 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -196,8 +196,8 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder
}
pbReq.Imp[i].TagID = strings.TrimSpace(adSlot[0])
- pbReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height))
pbReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width))
+ pbReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height))
if len(params.Keywords) != 0 {
kvstr := prepareImpressionExt(params.Keywords)
@@ -564,9 +564,8 @@ func validateAdSlot(adslot string, imp *openrtb2.Imp) error {
}
//In case of video, size could be derived from the player size
- if imp.Banner != nil && height != 0 && width != 0 {
- imp.Banner.H = openrtb2.Int64Ptr(int64(height))
- imp.Banner.W = openrtb2.Int64Ptr(int64(width))
+ if imp.Banner != nil {
+ imp.Banner = assignBannerWidthAndHeight(imp.Banner, int64(width), int64(height))
}
} else {
return errors.New(fmt.Sprintf("Invalid adSlot %v", adSlotStr))
@@ -575,25 +574,23 @@ func validateAdSlot(adslot string, imp *openrtb2.Imp) error {
return nil
}
-func assignBannerSize(banner *openrtb2.Banner) error {
- if banner == nil {
- return nil
- }
-
+func assignBannerSize(banner *openrtb2.Banner) (*openrtb2.Banner, error) {
if banner.W != nil && banner.H != nil {
- return nil
+ return banner, nil
}
if len(banner.Format) == 0 {
- return errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format))
+ return nil, errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format))
}
- banner.W = new(int64)
- *banner.W = banner.Format[0].W
- banner.H = new(int64)
- *banner.H = banner.Format[0].H
+ return assignBannerWidthAndHeight(banner, banner.Format[0].W, banner.Format[0].H), nil
+}
- return nil
+func assignBannerWidthAndHeight(banner *openrtb2.Banner, w, h int64) *openrtb2.Banner {
+ bannerCopy := *banner
+ bannerCopy.W = openrtb2.Int64Ptr(w)
+ bannerCopy.H = openrtb2.Int64Ptr(h)
+ return &bannerCopy
}
// parseImpressionObject parse the imp to get it ready to send to pubmatic
@@ -635,14 +632,14 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *pubmaticWrapperExt, pubID
}
if imp.Banner != nil {
- if err := assignBannerSize(imp.Banner); err != nil {
+ bannerCopy, err := assignBannerSize(imp.Banner)
+ if err != nil {
return err
}
+ imp.Banner = bannerCopy
}
- imp.Ext = nil
-
- impExtMap := make(map[string]interface{})
+ impExtMap := make(map[string]interface{}, 0)
if pubmaticExt.Keywords != nil && len(pubmaticExt.Keywords) != 0 {
addKeywordsToExt(pubmaticExt.Keywords, impExtMap)
}
diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go
index b0df1903a42..1a15f221130 100644
--- a/adapters/pubmatic/pubmatic_test.go
+++ b/adapters/pubmatic/pubmatic_test.go
@@ -73,7 +73,7 @@ func DummyPubMaticServer(w http.ResponseWriter, r *http.Request) {
var bids []openrtb2.Bid
for i, imp := range breq.Imp {
- bid := openrtb2.Bid{
+ bids = append(bids, openrtb2.Bid{
ID: fmt.Sprintf("SeatID_%d", i),
ImpID: imp.ID,
Price: float64(int(rand.Float64()*1000)) / 100,
@@ -83,9 +83,7 @@ func DummyPubMaticServer(w http.ResponseWriter, r *http.Request) {
W: *imp.Banner.W,
H: *imp.Banner.H,
DealID: fmt.Sprintf("DealID_%d", i),
- }
-
- bids = append(bids, bid)
+ })
}
resp.SeatBid[0].Bid = bids
diff --git a/adapters/pubmatic/pubmatictest/exemplary/simple-banner.json b/adapters/pubmatic/pubmatictest/exemplary/banner.json
similarity index 100%
rename from adapters/pubmatic/pubmatictest/exemplary/simple-banner.json
rename to adapters/pubmatic/pubmatictest/exemplary/banner.json
diff --git a/adapters/pubmatic/pubmatictest/params/race/video.json b/adapters/pubmatic/pubmatictest/params/race/video.json
index 770a3e1d4ab..86317f86e4c 100644
--- a/adapters/pubmatic/pubmatictest/params/race/video.json
+++ b/adapters/pubmatic/pubmatictest/params/race/video.json
@@ -1,6 +1,8 @@
{
"publisherId": "156209",
"adSlot": "pubmatic_test2@300x250",
+ "pmzoneid": "drama,sport",
+ "dctr": "abBucket=4|adType=page|entity=|paidByCategory=|sku=|userLevel=free|platform=android|majorVersion=3.54|version=3.54.0|mobileApplication=true|showId=20166|show=Kisah Untuk Geri|genre=Drama|contentUrl=https://www.iflix.com/title/show/20166|rating=TV-MA|contentLanguage=id",
"keywords": {
"pmzoneid": "Zone1,Zone2",
"dctr": "abBucket=4|adType=page|entity=|paidByCategory=|sku=|userLevel=free|platform=android|majorVersion=3.54|version=3.54.0|mobileApplication=true|showId=20166|show=Kisah Untuk Geri|genre=Drama|contentUrl=https://www.iflix.com/title/show/20166|rating=TV-MA|contentLanguage=id",
diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json
index ce4e4f854b2..ee763ce1c35 100644
--- a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json
+++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json
@@ -42,7 +42,8 @@
"adserver": {
"name": "gam",
"adslot": "/1111/home"
- }
+ },
+ "pbadslot": "/2222/home"
}
}
}
@@ -172,4 +173,4 @@
]
}
]
-}
+}
\ No newline at end of file
diff --git a/adapters/pubmatic/pubmatictest/supplemental/noAdSlot.json b/adapters/pubmatic/pubmatictest/supplemental/noAdSlot.json
new file mode 100644
index 00000000000..a277ef530e4
--- /dev/null
+++ b/adapters/pubmatic/pubmatictest/supplemental/noAdSlot.json
@@ -0,0 +1,129 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "999",
+ "keywords": [{
+ "key": "pmZoneID",
+ "value": ["Zone1", "Zone2"]
+ },
+ {
+ "key": "preference",
+ "value": ["sports", "movies"]
+ }
+ ],
+ "wrapper": {
+ "version": 1,
+ "profile": 5123
+ }
+ }
+ }
+ }],
+ "device": {
+ "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+ },
+ "site": {
+ "id": "siteID",
+ "publisher": {
+ "id": "1234"
+ }
+ }
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server",
+ "body": {
+ "id": "test-request-id",
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "pmZoneId": "Zone1,Zone2",
+ "preference": "sports,movies"
+ }
+ }],
+ "device": {
+ "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+ },
+ "site": {
+ "id": "siteID",
+ "publisher": {
+ "id": "999"
+ }
+ },
+ "ext": {
+ "wrapper": {
+ "profile": 5123,
+ "version": 1
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [{
+ "seat": "958",
+ "bid": [{
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": ["pubmatic.com"],
+ "crid": "29681110",
+ "w": 300,
+ "h": 250,
+ "dealid": "test deal",
+ "ext": {
+ "dspid": 6,
+ "deal_channel": 1
+ }
+ }]
+ }],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }],
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "bids": [{
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": ["pubmatic.com"],
+ "crid": "29681110",
+ "w": 300,
+ "h": 250,
+ "dealid": "test deal",
+ "ext": {
+ "dspid": 6,
+ "deal_channel": 1
+ }
+ },
+ "type": "banner"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go
index 89d69522fe8..e9627916cc6 100644
--- a/adapters/rubicon/rubicon.go
+++ b/adapters/rubicon/rubicon.go
@@ -91,21 +91,21 @@ type rubiconExtUserTpID struct {
UID string `json:"uid"`
}
-type rubiconUserDataExt struct {
- TaxonomyName string `json:"taxonomyname"`
+type rubiconDataExt struct {
+ SegTax int `json:"segtax"`
}
type rubiconUserExt struct {
- Consent string `json:"consent,omitempty"`
- DigiTrust *openrtb_ext.ExtUserDigiTrust `json:"digitrust"`
- Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"`
- TpID []rubiconExtUserTpID `json:"tpid,omitempty"`
- RP rubiconUserExtRP `json:"rp"`
- LiverampIdl string `json:"liveramp_idl,omitempty"`
+ Consent string `json:"consent,omitempty"`
+ Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"`
+ TpID []rubiconExtUserTpID `json:"tpid,omitempty"`
+ RP rubiconUserExtRP `json:"rp"`
+ LiverampIdl string `json:"liveramp_idl,omitempty"`
}
type rubiconSiteExtRP struct {
- SiteID int `json:"site_id"`
+ SiteID int `json:"site_id"`
+ Target json.RawMessage `json:"target,omitempty"`
}
type rubiconSiteExt struct {
@@ -676,7 +676,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
numRequests := len(request.Imp)
errs := make([]error, 0, len(request.Imp))
var err error
-
requestData := make([]*adapters.RequestData, 0, numRequests)
headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
@@ -750,14 +749,29 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
continue
}
+ resolvedBidFloor, err := resolveBidFloor(thisImp.BidFloor, thisImp.BidFloorCur, reqInfo)
+ if err != nil {
+ errs = append(errs, &errortypes.BadInput{
+ Message: fmt.Sprintf("Unable to convert provided bid floor currency from %s to USD",
+ thisImp.BidFloorCur),
+ })
+ continue
+ }
+
+ if resolvedBidFloor > 0 {
+ thisImp.BidFloorCur = "USD"
+ thisImp.BidFloor = resolvedBidFloor
+ }
+
if request.User != nil {
userCopy := *request.User
- userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}}
- if err := updateUserExtWithIabAttribute(&userExtRP, userCopy.Data); err != nil {
+ target, err := updateExtWithIabAttribute(rubiconExt.Visitor, userCopy.Data, []int{4})
+ if err != nil {
errs = append(errs, err)
continue
}
+ userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: target}}
if request.User.Ext != nil {
var userExt *openrtb_ext.ExtUser
@@ -768,9 +782,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
continue
}
userExtRP.Consent = userExt.Consent
- if userExt.DigiTrust != nil {
- userExtRP.DigiTrust = userExt.DigiTrust
- }
userExtRP.Eids = userExt.Eids
// set user.ext.tpid
@@ -845,19 +856,32 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
thisImp.Video = nil
}
- siteExt := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}}
pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: rubiconExt.AccountId}}
if request.Site != nil {
siteCopy := *request.Site
- siteCopy.Ext, err = json.Marshal(&siteExt)
+ siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}}
+ if siteCopy.Content != nil {
+ target, err := updateExtWithIabAttribute(nil, siteCopy.Content.Data, []int{1, 2})
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ siteExtRP.RP.Target = target
+ }
+
+ siteCopy.Ext, err = json.Marshal(&siteExtRP)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
siteCopy.Publisher = &openrtb2.Publisher{}
siteCopy.Publisher.Ext, err = json.Marshal(&pubExt)
rubiconRequest.Site = &siteCopy
- }
- if request.App != nil {
+ } else {
appCopy := *request.App
- appCopy.Ext, err = json.Marshal(&siteExt)
+ appCopy.Ext, err = json.Marshal(rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}})
appCopy.Publisher = &openrtb2.Publisher{}
appCopy.Publisher.Ext, err = json.Marshal(&pubExt)
rubiconRequest.App = &appCopy
@@ -893,41 +917,64 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
return requestData, errs
}
-func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error {
- var segmentIdsToCopy = make([]string, 0)
+func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.ExtraRequestInfo) (float64, error) {
+ if bidFloor > 0 && bidFloorCur != "" && strings.ToUpper(bidFloorCur) != "USD" {
+ return reqInfo.ConvertCurrency(bidFloor, bidFloorCur, "USD")
+ }
+
+ return bidFloor, nil
+}
+
+func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) {
+ var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes)
+ if len(segmentIdsToCopy) == 0 {
+ return target, nil
+ }
+
+ extRPTarget := make(map[string]interface{})
+
+ if target != nil {
+ if err := json.Unmarshal(target, &extRPTarget); err != nil {
+ return nil, &errortypes.BadInput{Message: err.Error()}
+ }
+ }
+
+ extRPTarget["iab"] = segmentIdsToCopy
+
+ jsonTarget, err := json.Marshal(&extRPTarget)
+ if err != nil {
+ return nil, &errortypes.BadInput{Message: err.Error()}
+ }
+ return jsonTarget, nil
+}
+
+func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string {
+ var segmentIdsToCopy = make([]string, 0, len(data))
for _, dataRecord := range data {
if dataRecord.Ext != nil {
- var dataExtObject rubiconUserDataExt
+ var dataExtObject rubiconDataExt
err := json.Unmarshal(dataRecord.Ext, &dataExtObject)
if err != nil {
continue
}
- if strings.EqualFold(dataExtObject.TaxonomyName, "iab") {
+ if contains(segTaxValues, dataExtObject.SegTax) {
for _, segment := range dataRecord.Segment {
segmentIdsToCopy = append(segmentIdsToCopy, segment.ID)
}
}
}
}
+ return segmentIdsToCopy
+}
- userExtRPTarget := make(map[string]interface{})
-
- if userExtRP.RP.Target != nil {
- if err := json.Unmarshal(userExtRP.RP.Target, &userExtRPTarget); err != nil {
- return &errortypes.BadInput{Message: err.Error()}
+func contains(s []int, e int) bool {
+ for _, a := range s {
+ if a == e {
+ return true
}
}
-
- userExtRPTarget["iab"] = segmentIdsToCopy
-
- if target, err := json.Marshal(&userExtRPTarget); err != nil {
- return &errortypes.BadInput{Message: err.Error()}
- } else {
- userExtRP.RP.Target = target
- }
-
- return nil
+ return false
}
func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) (mappedRubiconUidsParam, []error) {
diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go
index dc5b3a90423..28ddebbf5e3 100644
--- a/adapters/rubicon/rubicon_test.go
+++ b/adapters/rubicon/rubicon_test.go
@@ -4,6 +4,8 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
+ "github.com/stretchr/testify/mock"
"io/ioutil"
"net/http"
"net/http/httptest"
@@ -572,6 +574,125 @@ func TestResolveVideoSizeId(t *testing.T) {
}
}
+func TestOpenRTBRequestWithDifferentBidFloorAttributes(t *testing.T) {
+ testScenarios := []struct {
+ bidFloor float64
+ bidFloorCur string
+ setMock func(m *mock.Mock)
+ expectedBidFloor float64
+ expectedBidCur string
+ expectedErrors []error
+ }{
+ {
+ bidFloor: 1,
+ bidFloorCur: "WRONG",
+ setMock: func(m *mock.Mock) { m.On("GetRate", "WRONG", "USD").Return(2.5, errors.New("some error")) },
+ expectedBidFloor: 0,
+ expectedBidCur: "",
+ expectedErrors: []error{
+ &errortypes.BadInput{Message: "Unable to convert provided bid floor currency from WRONG to USD"},
+ },
+ },
+ {
+ bidFloor: 1,
+ bidFloorCur: "USD",
+ setMock: func(m *mock.Mock) {},
+ expectedBidFloor: 1,
+ expectedBidCur: "USD",
+ expectedErrors: nil,
+ },
+ {
+ bidFloor: 1,
+ bidFloorCur: "EUR",
+ setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USD").Return(1.2, nil) },
+ expectedBidFloor: 1.2,
+ expectedBidCur: "USD",
+ expectedErrors: nil,
+ },
+ {
+ bidFloor: 0,
+ bidFloorCur: "",
+ setMock: func(m *mock.Mock) {},
+ expectedBidFloor: 0,
+ expectedBidCur: "",
+ expectedErrors: nil,
+ },
+ {
+ bidFloor: -1,
+ bidFloorCur: "CZK",
+ setMock: func(m *mock.Mock) {},
+ expectedBidFloor: -1,
+ expectedBidCur: "CZK",
+ expectedErrors: nil,
+ },
+ }
+
+ for _, scenario := range testScenarios {
+ mockConversions := &mockCurrencyConversion{}
+ scenario.setMock(&mockConversions.Mock)
+
+ extraRequestInfo := adapters.ExtraRequestInfo{
+ CurrencyConversions: mockConversions,
+ }
+
+ SIZE_ID := getTestSizes()
+ bidder := new(RubiconAdapter)
+
+ request := &openrtb2.BidRequest{
+ ID: "test-request-id",
+ Imp: []openrtb2.Imp{{
+ ID: "test-imp-id",
+ BidFloorCur: scenario.bidFloorCur,
+ BidFloor: scenario.bidFloor,
+ Banner: &openrtb2.Banner{
+ Format: []openrtb2.Format{
+ SIZE_ID[15],
+ SIZE_ID[10],
+ },
+ },
+ Ext: json.RawMessage(`{"bidder": {
+ "zoneId": 8394,
+ "siteId": 283282,
+ "accountId": 7891
+ }}`),
+ }},
+ App: &openrtb2.App{
+ ID: "com.test",
+ Name: "testApp",
+ },
+ }
+
+ reqs, errs := bidder.MakeRequests(request, &extraRequestInfo)
+
+ mockConversions.AssertExpectations(t)
+
+ if scenario.expectedErrors == nil {
+ rubiconReq := &openrtb2.BidRequest{}
+ if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil {
+ t.Fatalf("Unexpected error while decoding request: %s", err)
+ }
+ assert.Equal(t, scenario.expectedBidFloor, rubiconReq.Imp[0].BidFloor)
+ assert.Equal(t, scenario.expectedBidCur, rubiconReq.Imp[0].BidFloorCur)
+ } else {
+ assert.Equal(t, scenario.expectedErrors, errs)
+ }
+ }
+}
+
+type mockCurrencyConversion struct {
+ mock.Mock
+}
+
+func (m mockCurrencyConversion) GetRate(from string, to string) (float64, error) {
+ args := m.Called(from, to)
+ return args.Get(0).(float64), args.Error(1)
+}
+
+func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 {
+ args := m.Called()
+ return args.Get(0).(*map[string]map[string]float64)
+}
+
func TestNoContentResponse(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
@@ -989,15 +1110,15 @@ func TestOpenRTBRequest(t *testing.T) {
}
}}`),
}},
+ App: &openrtb2.App{
+ ID: "com.test",
+ Name: "testApp",
+ },
Device: &openrtb2.Device{
PxRatio: rubidata.devicePxRatio,
},
User: &openrtb2.User{
- Ext: json.RawMessage(`{"digitrust": {
- "id": "some-digitrust-id",
- "keyv": 1,
- "pref": 0
- },
+ Ext: json.RawMessage(`{
"eids": [{
"source": "pubcid",
"id": "2402fc76-7b39-4f0e-bfc2-060ef7693648"
@@ -1071,10 +1192,6 @@ func TestOpenRTBRequest(t *testing.T) {
t.Fatal("Error unmarshalling request.user.ext object.")
}
- assert.Equal(t, "some-digitrust-id", userExt.DigiTrust.ID, "DigiTrust ID id not as expected!")
- assert.Equal(t, 1, userExt.DigiTrust.KeyV, "DigiTrust KeyV id not as expected!")
- assert.Equal(t, 0, userExt.DigiTrust.Pref, "DigiTrust Pref id not as expected!")
-
assert.NotNil(t, userExt.Eids)
assert.Equal(t, 1, len(userExt.Eids), "Eids values are not as expected!")
assert.Contains(t, userExt.Eids, openrtb_ext.ExtUserEid{Source: "pubcid", ID: "2402fc76-7b39-4f0e-bfc2-060ef7693648"})
@@ -1108,6 +1225,10 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) {
"visitor": {"key2" : "val2"}
}}`),
}},
+ App: &openrtb2.App{
+ ID: "com.test",
+ Name: "testApp",
+ },
}
reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{})
@@ -1157,6 +1278,10 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) {
}
}`),
}},
+ App: &openrtb2.App{
+ ID: "com.test",
+ Name: "testApp",
+ },
}
reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{})
@@ -1212,6 +1337,10 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) {
}
}`),
}},
+ App: &openrtb2.App{
+ ID: "com.test",
+ Name: "testApp",
+ },
}
reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{})
@@ -1245,6 +1374,10 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) {
"accountId": 7891
}}`),
}},
+ App: &openrtb2.App{
+ ID: "com.test",
+ Name: "testApp",
+ },
User: &openrtb2.User{
Ext: json.RawMessage(`{"eids": [
{
@@ -1353,6 +1486,10 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t
"video": {"size_id": 1}
}}`),
}},
+ App: &openrtb2.App{
+ ID: "com.test",
+ Name: "testApp",
+ },
}
reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{})
@@ -1398,6 +1535,10 @@ func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T)
"video": {"size_id": 1}
}}`),
}},
+ App: &openrtb2.App{
+ ID: "com.test",
+ Name: "testApp",
+ },
}
reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{})
diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json
index b85c28def44..1daffe9b386 100644
--- a/adapters/rubicon/rubicontest/exemplary/simple-video.json
+++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json
@@ -9,11 +9,50 @@
"id": "1",
"bundle": "com.wls.testwlsapplication"
},
+ "site": {
+ "content": {
+ "data": [
+ {
+ "ext": {
+ "segtax": 1
+ },
+ "segment": [
+ {
+ "id": "segmentId1"
+ },
+ {
+ "id": "segmentId2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "1"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": 2
+ },
+ "segment": [
+ {
+ "id": "segmentId3"
+ }
+ ]
+ }
+ ]
+ }
+ },
"user": {
"data": [
{
"ext": {
- "taxonomyname": "iab"
+ "segtax": 4
},
"segment": [
{
@@ -23,7 +62,7 @@
},
{
"ext": {
- "taxonomyname": "someValue"
+ "segtax": "someValue"
},
"segment": [
{
@@ -33,7 +72,17 @@
},
{
"ext": {
- "taxonomyname": "IaB"
+ "segtax": "4"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": 4
},
"segment": [
{
@@ -43,13 +92,13 @@
},
{
"ext": {
- "taxonomyname": [
- "wrong iab type"
+ "segtax": [
+ 4
]
},
"segment": [
{
- "id": "shouldNotBeCopied2"
+ "id": "shouldNotBeCopied3"
}
]
}
@@ -98,11 +147,69 @@
"ip": "123.123.123.123",
"ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
},
+ "site": {
+ "content": {
+ "data": [
+ {
+ "ext": {
+ "segtax": 1
+ },
+ "segment": [
+ {
+ "id": "segmentId1"
+ },
+ {
+ "id": "segmentId2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "1"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": 2
+ },
+ "segment": [
+ {
+ "id": "segmentId3"
+ }
+ ]
+ }
+ ]
+ },
+ "ext": {
+ "rp": {
+ "site_id": 113932,
+ "target": {
+ "iab": [
+ "segmentId1",
+ "segmentId2",
+ "segmentId3"
+ ]
+ }
+ }
+ },
+ "publisher": {
+ "ext": {
+ "rp": {
+ "account_id": 1001
+ }
+ }
+ }
+ },
"user": {
"data": [
{
"ext": {
- "taxonomyname": "iab"
+ "segtax": 4
},
"segment": [
{
@@ -112,7 +219,7 @@
},
{
"ext": {
- "taxonomyname": "someValue"
+ "segtax": "someValue"
},
"segment": [
{
@@ -122,7 +229,17 @@
},
{
"ext": {
- "taxonomyname": "IaB"
+ "segtax": "4"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": 4
},
"segment": [
{
@@ -132,19 +249,18 @@
},
{
"ext": {
- "taxonomyname": [
- "wrong iab type"
+ "segtax": [
+ 4
]
},
"segment": [
{
- "id": "shouldNotBeCopied2"
+ "id": "shouldNotBeCopied3"
}
]
}
],
"ext": {
- "digitrust": null,
"rp": {
"target": {
"iab": [
@@ -157,18 +273,6 @@
},
"app": {
"id": "1",
- "ext": {
- "rp": {
- "site_id": 113932
- }
- },
- "publisher": {
- "ext": {
- "rp": {
- "account_id": 1001
- }
- }
- },
"bundle": "com.wls.testwlsapplication"
},
"imp": [
diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json
new file mode 100644
index 00000000000..0be214da4bc
--- /dev/null
+++ b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json
@@ -0,0 +1,289 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "device": {
+ "ip": "123.123.123.123",
+ "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+ },
+ "app": {
+ "id": "1",
+ "bundle": "com.wls.testwlsapplication"
+ },
+ "site": {
+ "content": {
+ }
+ },
+ "user": {
+ "data": [
+ {
+ "ext": {
+ "segtax": 4
+ },
+ "segment": [
+ {
+ "id": "idToCopy"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "someValue"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "4"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": 4
+ },
+ "segment": [
+ {
+ "id": "idToCopy2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": [
+ 4
+ ]
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied3"
+ }
+ ]
+ }
+ ]
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "instl": 1,
+ "video": {
+ "placement": 3,
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 1024,
+ "h": 576
+ },
+ "ext": {
+ "bidder": {
+ "video": {
+ },
+ "accountId": 1001,
+ "siteId": 113932,
+ "zoneId": 535510
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "uri?tk_xint=pbs-test-tracker",
+ "body": {
+ "id": "test-request-id",
+ "device": {
+ "ext": {
+ "rp": {
+ "pixelratio": 0
+ }
+ },
+ "ip": "123.123.123.123",
+ "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+ },
+ "site": {
+ "content": {
+ },
+ "ext": {
+ "rp": {
+ "site_id": 113932
+ }
+ },
+ "publisher": {
+ "ext": {
+ "rp": {
+ "account_id": 1001
+ }
+ }
+ }
+ },
+ "user": {
+ "data": [
+ {
+ "ext": {
+ "segtax": 4
+ },
+ "segment": [
+ {
+ "id": "idToCopy"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "someValue"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "4"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": 4
+ },
+ "segment": [
+ {
+ "id": "idToCopy2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": [
+ 4
+ ]
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied3"
+ }
+ ]
+ }
+ ],
+ "ext": {
+ "rp": {
+ "target": {
+ "iab": [
+ "idToCopy",
+ "idToCopy2"
+ ]
+ }
+ }
+ }
+ },
+ "app": {
+ "id": "1",
+ "bundle": "com.wls.testwlsapplication"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "instl": 1,
+ "video": {
+ "placement": 3,
+ "ext": {
+ "rp": {
+ "size_id": 203
+ }
+ },
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 1024,
+ "h": 576
+ },
+ "ext": {
+ "rp": {
+ "track": {
+ "mint": "",
+ "mint_version": ""
+ },
+ "zone_id": 535510
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ }
+ ],
+ "seat": "adman"
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content.json b/adapters/rubicon/rubicontest/supplemental/no-site-content.json
new file mode 100644
index 00000000000..2e830a2dd00
--- /dev/null
+++ b/adapters/rubicon/rubicontest/supplemental/no-site-content.json
@@ -0,0 +1,285 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "device": {
+ "ip": "123.123.123.123",
+ "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+ },
+ "app": {
+ "id": "1",
+ "bundle": "com.wls.testwlsapplication"
+ },
+ "site": {
+ },
+ "user": {
+ "data": [
+ {
+ "ext": {
+ "segtax": 4
+ },
+ "segment": [
+ {
+ "id": "idToCopy"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "someValue"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "4"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": 4
+ },
+ "segment": [
+ {
+ "id": "idToCopy2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": [
+ 4
+ ]
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied3"
+ }
+ ]
+ }
+ ]
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "instl": 1,
+ "video": {
+ "placement": 3,
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 1024,
+ "h": 576
+ },
+ "ext": {
+ "bidder": {
+ "video": {
+ },
+ "accountId": 1001,
+ "siteId": 113932,
+ "zoneId": 535510
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "uri?tk_xint=pbs-test-tracker",
+ "body": {
+ "id": "test-request-id",
+ "device": {
+ "ext": {
+ "rp": {
+ "pixelratio": 0
+ }
+ },
+ "ip": "123.123.123.123",
+ "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+ },
+ "site": {
+ "ext": {
+ "rp": {
+ "site_id": 113932
+ }
+ },
+ "publisher": {
+ "ext": {
+ "rp": {
+ "account_id": 1001
+ }
+ }
+ }
+ },
+ "user": {
+ "data": [
+ {
+ "ext": {
+ "segtax": 4
+ },
+ "segment": [
+ {
+ "id": "idToCopy"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "someValue"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": "4"
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": 4
+ },
+ "segment": [
+ {
+ "id": "idToCopy2"
+ }
+ ]
+ },
+ {
+ "ext": {
+ "segtax": [
+ 4
+ ]
+ },
+ "segment": [
+ {
+ "id": "shouldNotBeCopied3"
+ }
+ ]
+ }
+ ],
+ "ext": {
+ "rp": {
+ "target": {
+ "iab": [
+ "idToCopy",
+ "idToCopy2"
+ ]
+ }
+ }
+ }
+ },
+ "app": {
+ "id": "1",
+ "bundle": "com.wls.testwlsapplication"
+ },
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "instl": 1,
+ "video": {
+ "placement": 3,
+ "ext": {
+ "rp": {
+ "size_id": 203
+ }
+ },
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 1024,
+ "h": 576
+ },
+ "ext": {
+ "rp": {
+ "track": {
+ "mint": "",
+ "mint_version": ""
+ },
+ "zone_id": 535510
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ }
+ ],
+ "seat": "adman"
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test_bid_id",
+ "impid": "test-imp-id",
+ "price": 0.27543,
+ "adm": "some-test-ad",
+ "cid": "test_cid",
+ "crid": "test_crid",
+ "dealid": "test_dealid",
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/sa_lunamedia/params_test.go b/adapters/sa_lunamedia/params_test.go
new file mode 100644
index 00000000000..bf7a1f493e6
--- /dev/null
+++ b/adapters/sa_lunamedia/params_test.go
@@ -0,0 +1,52 @@
+package salunamedia
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+var validParams = []string{
+ `{ "key": "2", "type": "network"}`,
+ `{ "key": "1"}`,
+ `{ "key": "33232", "type": "publisher"}`,
+}
+
+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.BidderSaLunaMedia, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected sa_lunamedia params: %s", validParam)
+ }
+ }
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `[]`,
+ `{}`,
+ `{ "anyparam": "anyvalue" }`,
+ `{ "type": "network" }`,
+ `{ "key": "asddsfd", "type": "any"}`,
+}
+
+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.BidderSaLunaMedia, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
diff --git a/adapters/sa_lunamedia/salunamedia.go b/adapters/sa_lunamedia/salunamedia.go
new file mode 100644
index 00000000000..ea6e12b01d6
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamedia.go
@@ -0,0 +1,132 @@
+package salunamedia
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/openrtb_ext"
+ "net/http"
+)
+
+type adapter struct {
+ endpoint string
+}
+
+type bidExt struct {
+ MediaType string `json:"mediaType"`
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+ bidder := &adapter{
+ endpoint: config.Endpoint,
+ }
+ return bidder, nil
+}
+
+func (a *adapter) MakeRequests(
+ openRTBRequest *openrtb2.BidRequest,
+ reqInfo *adapters.ExtraRequestInfo,
+) (
+ requestsToBidder []*adapters.RequestData,
+ errs []error,
+) {
+
+ reqJSON, err := json.Marshal(openRTBRequest)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ return []*adapters.RequestData{{
+ Method: http.MethodPost,
+ Body: reqJSON,
+ Uri: a.endpoint,
+ Headers: headers,
+ }}, nil
+}
+
+func (a *adapter) MakeBids(
+ openRTBRequest *openrtb2.BidRequest,
+ requestToBidder *adapters.RequestData,
+ bidderRawResponse *adapters.ResponseData,
+) (
+ bidderResponse *adapters.BidderResponse,
+ errs []error,
+) {
+ if bidderRawResponse.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if bidderRawResponse.StatusCode == http.StatusBadRequest {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)),
+ }}
+ }
+
+ if bidderRawResponse.StatusCode == http.StatusServiceUnavailable {
+ return nil, []error{&errortypes.BadInput{
+ Message: "Bidder unavailable. Please contact the bidder support.",
+ }}
+ }
+
+ if bidderRawResponse.StatusCode != http.StatusOK {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Status Code: [ %d ] %s", bidderRawResponse.StatusCode, string(bidderRawResponse.Body)),
+ }}
+ }
+
+ responseBody := bidderRawResponse.Body
+ var bidResp openrtb2.BidResponse
+ if err := json.Unmarshal(responseBody, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ if len(bidResp.SeatBid) == 0 {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: "Empty SeatBid",
+ }}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+
+ bids := bidResp.SeatBid[0].Bid
+
+ if len(bids) == 0 {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: "Empty SeatBid.Bids",
+ }}
+ }
+
+ bid := bids[0]
+
+ var bidExt bidExt
+ var bidType openrtb_ext.BidType
+
+ if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: "Missing BidExt",
+ }}
+ }
+
+ bidType, err := getBidType(bidExt)
+
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: bidType,
+ })
+ return bidResponse, nil
+}
+
+func getBidType(ext bidExt) (openrtb_ext.BidType, error) {
+ return openrtb_ext.ParseBidType(ext.MediaType)
+}
diff --git a/adapters/sa_lunamedia/salunamedia_test.go b/adapters/sa_lunamedia/salunamedia_test.go
new file mode 100644
index 00000000000..f5d2058208e
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamedia_test.go
@@ -0,0 +1,18 @@
+package salunamedia
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderSaLunaMedia, config.Adapter{
+ Endpoint: "http://test.com/pserver"})
+
+ assert.NoError(t, buildErr)
+ adapterstest.RunJSONBidderTest(t, "salunamediatest", bidder)
+}
diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json b/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json
new file mode 100644
index 00000000000..2ce4ad81106
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json
@@ -0,0 +1,142 @@
+{
+ "mockBidRequest": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://test.com/pserver",
+ "body": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "id",
+ "bidid": "id",
+ "seatbid": [{
+ "bid": [{
+ "id": "id",
+ "impid": "id",
+ "price": 0.1,
+ "nurl": "http://test.com/nurl",
+ "burl": "http://test.com/burl",
+ "adm": "Test",
+ "adomain": ["test.com"],
+ "cat": ["IAB1"],
+ "cid": "cid",
+ "crid": "crid",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "mediaType": "banner"
+ }
+ }],
+ "seat": "seat"
+ }],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "id",
+ "impid": "id",
+ "price": 0.1,
+ "nurl": "http://test.com/nurl",
+ "burl": "http://test.com/burl",
+ "adm": "Test",
+ "adomain": ["test.com"],
+ "cat": ["IAB1"],
+ "cid": "cid",
+ "crid": "crid",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "mediaType": "banner"
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/native.json b/adapters/sa_lunamedia/salunamediatest/exemplary/native.json
new file mode 100644
index 00000000000..74d8940f0a1
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/exemplary/native.json
@@ -0,0 +1,132 @@
+{
+ "mockBidRequest": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "native": {
+ "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "http://test.com/pserver",
+ "body": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "native": {
+ "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+ "ver": "1.1"
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "id",
+ "bidid": "id",
+ "seatbid": [{
+ "bid": [{
+ "id": "id",
+ "impid": "id",
+ "price": 0.1,
+ "nurl": "http://test.com/nurl",
+ "burl": "http://test.com/burl",
+ "adm": "{}",
+ "adomain": ["test.com"],
+ "cat": ["IAB1"],
+ "cid": "cid",
+ "crid": "crid",
+ "ext": {
+ "mediaType": "native"
+ }
+ }],
+ "seat": "seat"
+ }],
+ "cur": "USD"
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "USD",
+ "bids": [{
+ "bid": {
+ "id": "id",
+ "impid": "id",
+ "price": 0.1,
+ "nurl": "http://test.com/nurl",
+ "burl": "http://test.com/burl",
+ "adm": "{}",
+ "adomain": ["test.com"],
+ "cat": ["IAB1"],
+ "cid": "cid",
+ "crid": "crid",
+ "ext": {
+ "mediaType": "native"
+ }
+ },
+ "type": "native"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/video.json b/adapters/sa_lunamedia/salunamediatest/exemplary/video.json
new file mode 100644
index 00000000000..9a042d726d9
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/exemplary/video.json
@@ -0,0 +1,169 @@
+{
+ "mockBidRequest": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "video": {
+ "mimes": ["video/mp4", "video/ogg", "video/webm"],
+ "minduration": 3,
+ "maxduration": 3000,
+ "protocols": [2, 3, 5, 6, 7, 8],
+ "w": 480,
+ "h": 320,
+ "linearity": 1,
+ "playbackmethod": [2],
+ "pos": 0
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://test.com/pserver",
+ "body": {
+ "id": "id",
+ "imp": [
+ {
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/ogg",
+ "video/webm"
+ ],
+ "minduration": 3,
+ "maxduration": 3000,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 480,
+ "h": 320,
+ "linearity": 1,
+ "playbackmethod": [
+ 2
+ ],
+ "pos": 0
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": [
+ "IAB12"
+ ],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "id",
+ "bidid": "id",
+ "seatbid": [{
+ "bid": [{
+ "id": "id",
+ "impid": "id",
+ "price": 0.1,
+ "nurl": "http://test.com/nurl",
+ "burl": "http://test.com/burl",
+ "adm": "",
+ "adomain": ["test.com"],
+ "cat": ["IAB1"],
+ "cid": "cid",
+ "crid": "crid",
+ "ext": {
+ "mediaType": "video"
+ }
+ }],
+ "seat": "seat"
+ }],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "id",
+ "impid": "id",
+ "price": 0.1,
+ "nurl": "http://test.com/nurl",
+ "burl": "http://test.com/burl",
+ "adm": "",
+ "adomain": ["test.com"],
+ "cat": ["IAB1"],
+ "cid": "cid",
+ "crid": "crid",
+ "ext": {
+ "mediaType": "video"
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/banner.json b/adapters/sa_lunamedia/salunamediatest/params/race/banner.json
new file mode 100644
index 00000000000..c02a5de97a2
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+ "key": "test"
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/native.json b/adapters/sa_lunamedia/salunamediatest/params/race/native.json
new file mode 100644
index 00000000000..c02a5de97a2
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/params/race/native.json
@@ -0,0 +1,3 @@
+{
+ "key": "test"
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/video.json b/adapters/sa_lunamedia/salunamediatest/params/race/video.json
new file mode 100644
index 00000000000..c02a5de97a2
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+ "key": "test"
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json b/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json
new file mode 100644
index 00000000000..6373207d481
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json
@@ -0,0 +1,98 @@
+{
+ "mockBidRequest": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://test.com/pserver",
+ "body": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": ""
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json b/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json
new file mode 100644
index 00000000000..8942b3be65a
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json
@@ -0,0 +1,102 @@
+{
+ "mockBidRequest": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "http://test.com/pserver",
+ "body": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+ "bidid": "359da97d0384d8a14767029c18fd840d",
+ "seatbid": [],
+ "cur": "USD"
+ }
+ }
+ }],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Empty SeatBid",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json
new file mode 100644
index 00000000000..042b96bde65
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json
@@ -0,0 +1,92 @@
+{
+ "mockBidRequest": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://test.com/pserver",
+ "body": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json
new file mode 100644
index 00000000000..1ecdc46e5fa
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json
@@ -0,0 +1,99 @@
+{
+ "mockBidRequest": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://test.com/pserver",
+ "body": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 400,
+ "body": "The Key has a different ad format"
+ }
+ }
+ ],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Bad Request. \"The Key has a different ad format\"",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json
new file mode 100644
index 00000000000..2590418a75f
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json
@@ -0,0 +1,98 @@
+{
+ "mockBidRequest": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://test.com/pserver",
+ "body": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 503
+ }
+ }
+ ],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Bidder unavailable. Please contact the bidder support.",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json b/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json
new file mode 100644
index 00000000000..a54737cafdb
--- /dev/null
+++ b/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json
@@ -0,0 +1,99 @@
+{
+ "mockBidRequest": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://test.com/pserver",
+ "body": {
+ "id": "id",
+ "imp": [{
+ "id": "id",
+ "secure": 1,
+ "bidfloor": 0.01,
+ "bidfloorcur": "USD",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "key": "test"
+ }
+ }
+ }],
+ "device": {
+ "ua": "UA",
+ "ip": "123.3.4.123"
+ },
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ },
+ "user": {
+ "id": "userid"
+ },
+ "site": {
+ "id": "id",
+ "domain": "test,com",
+ "cat": ["IAB12"],
+ "publisher": {
+ "id": "pubid"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 403,
+ "body": "Access is denied"
+ }
+ }
+ ],
+
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Status Code: [ 403 ] \"Access is denied\"",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/sa_lunamedia/usersync.go b/adapters/sa_lunamedia/usersync.go
new file mode 100644
index 00000000000..f78b7944cb2
--- /dev/null
+++ b/adapters/sa_lunamedia/usersync.go
@@ -0,0 +1,12 @@
+package salunamedia
+
+import (
+ "text/template"
+
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/usersync"
+)
+
+func NewSaLunamediaSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("salunamedia", temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/sa_lunamedia/usersync_test.go b/adapters/sa_lunamedia/usersync_test.go
new file mode 100644
index 00000000000..e3820fbc1af
--- /dev/null
+++ b/adapters/sa_lunamedia/usersync_test.go
@@ -0,0 +1,33 @@
+package salunamedia
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/prebid/prebid-server/privacy"
+ "github.com/prebid/prebid-server/privacy/ccpa"
+ "github.com/prebid/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNewSaLunamediaSyncer(t *testing.T) {
+ syncURL := "https://test.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+ syncer := NewSaLunamediaSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "0",
+ Consent: "allGdpr",
+ },
+ CCPA: ccpa.Policy{
+ Consent: "1---",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "https://test.com/pserver?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/sharethrough/butler.go b/adapters/sharethrough/butler.go
index b34ae0844ab..b7dd5003e6a 100644
--- a/adapters/sharethrough/butler.go
+++ b/adapters/sharethrough/butler.go
@@ -21,6 +21,7 @@ const defaultTmax = 10000 // 10 sec
type StrAdSeverParams struct {
Pkey string
BidID string
+ GPID string
ConsentRequired bool
ConsentString string
USPrivacySignal string
@@ -97,6 +98,11 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb2.Imp, request *open
return nil, err
}
+ var gpid string
+ if strImpParams.Data != nil && strImpParams.Data.PBAdSlot != "" {
+ gpid = strImpParams.Data.PBAdSlot
+ }
+
usPolicySignal := ""
if usPolicy, err := ccpa.ReadFromRequest(request); err == nil {
usPolicySignal = usPolicy.Consent
@@ -107,6 +113,7 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb2.Imp, request *open
Uri: s.UriHelper.buildUri(StrAdSeverParams{
Pkey: pKey,
BidID: imp.ID,
+ GPID: gpid,
ConsentRequired: s.Util.gdprApplies(request),
ConsentString: userInfo.Consent,
USPrivacySignal: usPolicySignal,
@@ -191,6 +198,9 @@ func (h StrUriHelper) buildUri(params StrAdSeverParams) string {
v := url.Values{}
v.Set("placement_key", params.Pkey)
v.Set("bidId", params.BidID)
+ if params.GPID != "" {
+ v.Set("gpid", params.GPID)
+ }
v.Set("consent_required", fmt.Sprintf("%t", params.ConsentRequired))
v.Set("consent_string", params.ConsentString)
if params.USPrivacySignal != "" {
diff --git a/adapters/sharethrough/butler_test.go b/adapters/sharethrough/butler_test.go
index fbef417e530..3d19eea9171 100644
--- a/adapters/sharethrough/butler_test.go
+++ b/adapters/sharethrough/butler_test.go
@@ -83,7 +83,7 @@ func TestSuccessRequestFromOpenRTB(t *testing.T) {
"Generates the correct AdServer request from Imp (no user provided)": {
inputImp: openrtb2.Imp{
ID: "abc",
- Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true, "iframeSize": [10, 20], "bidfloor": 1.0} }`),
+ Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true, "iframeSize": [10, 20], "bidfloor": 1.0, "data": { "pbadslot": "adslot" } } }`),
Banner: &openrtb2.Banner{
Format: []openrtb2.Format{{H: 30, W: 40}},
},
@@ -435,6 +435,7 @@ func TestBuildUri(t *testing.T) {
inputParams: StrAdSeverParams{
Pkey: "pkey",
BidID: "bid",
+ GPID: "gpid",
ConsentRequired: true,
ConsentString: "consent",
USPrivacySignal: "ccpa",
@@ -449,6 +450,7 @@ func TestBuildUri(t *testing.T) {
"http://abc.com?",
"placement_key=pkey",
"bidId=bid",
+ "gpid=gpid",
"consent_required=true",
"consent_string=consent",
"us_privacy=ccpa",
diff --git a/adapters/smaato/image.go b/adapters/smaato/image.go
index 582206ccb0c..a4dad157bd1 100644
--- a/adapters/smaato/image.go
+++ b/adapters/smaato/image.go
@@ -3,6 +3,7 @@ package smaato
import (
"encoding/json"
"fmt"
+ "github.com/prebid/prebid-server/errortypes"
"net/url"
"strings"
)
@@ -22,32 +23,27 @@ type img struct {
Ctaurl string `json:"ctaurl"`
}
-func extractAdmImage(adapterResponseAdm string) (string, error) {
- var imgMarkup string
- var err error
-
+func extractAdmImage(adMarkup string) (string, error) {
var imageAd imageAd
- err = json.Unmarshal([]byte(adapterResponseAdm), &imageAd)
- var image = imageAd.Image
-
- if err == nil {
- var clickEvent strings.Builder
- var impressionTracker strings.Builder
-
- for _, clicktracker := range image.Clicktrackers {
- clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'.replace(/\\+/g, ' ')), " +
- "{cache: 'no-cache'});")
+ if err := json.Unmarshal([]byte(adMarkup), &imageAd); err != nil {
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup),
}
+ }
- for _, impression := range image.Impressiontrackers {
-
- impressionTracker.WriteString(fmt.Sprintf(``, impression))
- }
+ var clickEvent strings.Builder
+ for _, clicktracker := range imageAd.Image.Clicktrackers {
+ clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'.replace(/\\+/g, ' ')), " +
+ "{cache: 'no-cache'});")
+ }
- imgMarkup = fmt.Sprintf(`%s
`,
- &clickEvent, url.QueryEscape(image.Img.Ctaurl), image.
- Img.URL, image.Img.W, image.Img.
- H, &impressionTracker)
+ var impressionTracker strings.Builder
+ for _, impression := range imageAd.Image.Impressiontrackers {
+ impressionTracker.WriteString(fmt.Sprintf(``, impression))
}
- return imgMarkup, err
+
+ imageAdMarkup := fmt.Sprintf(`%s
`,
+ &clickEvent, url.QueryEscape(imageAd.Image.Img.Ctaurl), imageAd.Image.Img.URL, imageAd.Image.Img.W, imageAd.Image.Img.H, &impressionTracker)
+
+ return imageAdMarkup, nil
}
diff --git a/adapters/smaato/image_test.go b/adapters/smaato/image_test.go
index 5f39c857201..1ba99ddd7c5 100644
--- a/adapters/smaato/image_test.go
+++ b/adapters/smaato/image_test.go
@@ -1,44 +1,51 @@
package smaato
import (
+ "github.com/stretchr/testify/assert"
"testing"
)
-func TestRenderAdMarkup(t *testing.T) {
- type args struct {
- adType adMarkupType
- adapterResponseAdm string
- }
- expectedResult := ``
-
+func TestExtractAdmImage(t *testing.T) {
tests := []struct {
- testName string
- args args
- result string
+ testName string
+ adMarkup string
+ expectedAdMarkup string
+ expectedError string
}{
- {"imageTest", args{"Img",
- "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\"," +
+ {
+ testName: "extract image",
+ adMarkup: "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\"," +
"\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"}," +
"\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," +
- "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}"},
- expectedResult,
+ "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ expectedAdMarkup: ``,
+ expectedError: "",
+ },
+ {
+ testName: "invalid adMarkup",
+ adMarkup: "{",
+ expectedAdMarkup: "",
+ expectedError: "Invalid ad markup {.",
},
}
+
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
- got, err := renderAdMarkup(tt.args.adType, tt.args.adapterResponseAdm)
- if err != nil {
- t.Errorf("error rendering ad markup: %v", err)
- }
- if got != tt.result {
- t.Errorf("renderAdMarkup() got = %v, result %v", got, tt.result)
+ adMarkup, err := extractAdmImage(tt.adMarkup)
+
+ if tt.expectedError != "" {
+ assert.EqualError(t, err, tt.expectedError)
+ } else {
+ assert.NoError(t, err)
}
+
+ assert.Equal(t, tt.expectedAdMarkup, adMarkup)
})
}
}
diff --git a/adapters/smaato/params_test.go b/adapters/smaato/params_test.go
index 6c71cbe75c6..2e29550a394 100644
--- a/adapters/smaato/params_test.go
+++ b/adapters/smaato/params_test.go
@@ -41,6 +41,8 @@ func TestInvalidParams(t *testing.T) {
var validParams = []string{
`{"publisherId":"test-id-1234-smaato","adspaceId": "1123581321"}`,
+ `{"publisherId":"test-id-1234-smaato","adbreakId": "4123581321"}`,
+ `{"publisherId":"test-id-1234-smaato","adspaceId": "1123581321","adbreakId": "4123581321"}`,
}
var invalidParams = []string{
diff --git a/adapters/smaato/richmedia.go b/adapters/smaato/richmedia.go
index 1c94a3555c1..a8865361d38 100644
--- a/adapters/smaato/richmedia.go
+++ b/adapters/smaato/richmedia.go
@@ -3,6 +3,7 @@ package smaato
import (
"encoding/json"
"fmt"
+ "github.com/prebid/prebid-server/errortypes"
"net/url"
"strings"
)
@@ -22,31 +23,27 @@ type richmedia struct {
Clicktrackers []string `json:"clicktrackers"`
}
-func extractAdmRichMedia(adapterResponseAdm string) (string, error) {
- var richMediaMarkup string
- var err error
-
+func extractAdmRichMedia(adMarkup string) (string, error) {
var richMediaAd richMediaAd
- err = json.Unmarshal([]byte(adapterResponseAdm), &richMediaAd)
- var richMedia = richMediaAd.RichMedia
-
- if err == nil {
- var clickEvent strings.Builder
- var impressionTracker strings.Builder
-
- for _, clicktracker := range richMedia.Clicktrackers {
- clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'), " +
- "{cache: 'no-cache'});")
+ if err := json.Unmarshal([]byte(adMarkup), &richMediaAd); err != nil {
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup),
}
- for _, impression := range richMedia.Impressiontrackers {
+ }
- impressionTracker.WriteString(fmt.Sprintf(``, impression))
- }
+ var clickEvent strings.Builder
+ var impressionTracker strings.Builder
- richMediaMarkup = fmt.Sprintf(`%s%s
`,
- &clickEvent,
- richMedia.MediaData.Content,
- &impressionTracker)
+ for _, clicktracker := range richMediaAd.RichMedia.Clicktrackers {
+ clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'), " +
+ "{cache: 'no-cache'});")
+ }
+ for _, impression := range richMediaAd.RichMedia.Impressiontrackers {
+ impressionTracker.WriteString(fmt.Sprintf(``, impression))
}
- return richMediaMarkup, err
+
+ richmediaAdMarkup := fmt.Sprintf(`%s%s
`,
+ &clickEvent, richMediaAd.RichMedia.MediaData.Content, &impressionTracker)
+
+ return richmediaAdMarkup, nil
}
diff --git a/adapters/smaato/richmedia_test.go b/adapters/smaato/richmedia_test.go
index 20fa1ba353c..eff559852be 100644
--- a/adapters/smaato/richmedia_test.go
+++ b/adapters/smaato/richmedia_test.go
@@ -1,39 +1,48 @@
package smaato
import (
+ "github.com/stretchr/testify/assert"
"testing"
)
func TestExtractAdmRichMedia(t *testing.T) {
- type args struct {
- adType adMarkupType
- adapterResponseAdm string
- }
- expectedResult := `hello
` +
- `
`
tests := []struct {
- testName string
- args args
- result string
+ testName string
+ adMarkup string
+ expectedAdMarkup string
+ expectedError string
}{
- {"richmediaTest", args{"Richmedia", "{\"richmedia\":{\"mediadata\":{\"content\":\"hello
\"," +
- "" + "\"w\":350," +
- "\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," +
- "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}"},
- expectedResult,
+ {
+ testName: "extract richmedia",
+ adMarkup: "{\"richmedia\":{\"mediadata\":{\"content\":\"hello
\"," +
+ "" + "\"w\":350," +
+ "\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," +
+ "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ expectedAdMarkup: `hello
` +
+ `
`,
+ expectedError: "",
+ },
+ {
+ testName: "invalid adMarkup",
+ adMarkup: "{",
+ expectedAdMarkup: "",
+ expectedError: "Invalid ad markup {.",
},
}
+
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
- got, err := renderAdMarkup(tt.args.adType, tt.args.adapterResponseAdm)
- if err != nil {
- t.Errorf("error rendering ad markup: %v", err)
- }
- if got != tt.result {
- t.Errorf("renderAdMarkup() got = %v, result %v", got, tt.result)
+ adMarkup, err := extractAdmRichMedia(tt.adMarkup)
+
+ if tt.expectedError != "" {
+ assert.EqualError(t, err, tt.expectedError)
+ } else {
+ assert.NoError(t, err)
}
+
+ assert.Equal(t, tt.expectedAdMarkup, adMarkup)
})
}
}
diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go
index 9aea2e1e614..c84dd356a59 100644
--- a/adapters/smaato/smaato.go
+++ b/adapters/smaato/smaato.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
+ "strconv"
"strings"
"github.com/buger/jsonparser"
@@ -11,10 +12,12 @@ import (
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/metrics"
"github.com/prebid/prebid-server/openrtb_ext"
+ "github.com/prebid/prebid-server/util/timeutil"
)
-const clientVersion = "prebid_server_0.2"
+const clientVersion = "prebid_server_0.4"
type adMarkupType string
@@ -24,23 +27,20 @@ const (
smtAdTypeVideo adMarkupType = "Video"
)
-// SmaatoAdapter describes a Smaato prebid server adapter.
-type SmaatoAdapter struct {
- URI string
-}
-
-//userExt defines User.Ext object for Smaato
-type userExt struct {
- Data userExtData `json:"data"`
+// adapter describes a Smaato prebid server adapter.
+type adapter struct {
+ clock timeutil.Time
+ endpoint string
}
+// userExtData defines User.Ext.Data object for Smaato
type userExtData struct {
Keywords string `json:"keywords"`
Gender string `json:"gender"`
Yob int64 `json:"yob"`
}
-//userExt defines Site.Ext object for Smaato
+// siteExt defines Site.Ext object for Smaato
type siteExt struct {
Data siteExtData `json:"data"`
}
@@ -49,189 +49,253 @@ type siteExtData struct {
Keywords string `json:"keywords"`
}
+// bidRequestExt defines BidRequest.Ext object for Smaato
+type bidRequestExt struct {
+ Client string `json:"client"`
+}
+
+// bidExt defines Bid.Ext object for Smaato
+type bidExt struct {
+ Duration int `json:"duration"`
+}
+
+// videoExt defines Video.Ext object for Smaato
+type videoExt struct {
+ Context string `json:"context,omitempty"`
+}
+
// Builder builds a new instance of the Smaato adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
- bidder := &SmaatoAdapter{
- URI: config.Endpoint,
+ bidder := &adapter{
+ clock: &timeutil.RealTime{},
+ endpoint: config.Endpoint,
}
return bidder, nil
}
// MakeRequests makes the HTTP requests which should be made to fetch bids.
-func (a *SmaatoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
- errs := make([]error, 0, len(request.Imp))
+func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
if len(request.Imp) == 0 {
- errs = append(errs, &errortypes.BadInput{Message: "no impressions in bid request"})
- return nil, errs
+ return nil, []error{&errortypes.BadInput{Message: "No impressions in bid request."}}
}
- // Use bidRequestExt of first imp to retrieve params which are valid for all imps, e.g. publisherId
- publisherID, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId")
- if err != nil {
- errs = append(errs, err)
- return nil, errs
+ // set data in request that is common for all requests
+ if err := prepareCommonRequest(request); err != nil {
+ return nil, []error{err}
}
- for i := 0; i < len(request.Imp); i++ {
- err := parseImpressionObject(&request.Imp[i])
- // If the parsing is failed, remove imp and add the error.
- if err != nil {
- errs = append(errs, err)
- request.Imp = append(request.Imp[:i], request.Imp[i+1:]...)
- i--
+ isVideoEntryPoint := reqInfo.PbsEntryPoint == metrics.ReqTypeVideo
+
+ if isVideoEntryPoint {
+ return adapter.makePodRequests(request)
+ } else {
+ return adapter.makeIndividualRequests(request)
+ }
+}
+
+// MakeBids unpacks the server's response into Bids.
+func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ 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 openrtb2.BidResponse
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+
+ var errors []error
+ for _, seatBid := range bidResp.SeatBid {
+ for i := 0; i < len(seatBid.Bid); i++ {
+ bid := seatBid.Bid[i]
+
+ adMarkupType, err := getAdMarkupType(response, bid.AdM)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ bid.AdM, err = renderAdMarkup(adMarkupType, bid.AdM)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ bidType, err := convertAdMarkupTypeToMediaType(adMarkupType)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ bidVideo, err := buildBidVideo(&bid, bidType)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ bid.Exp = adapter.getTTLFromHeaderOrDefault(response)
+
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: bidType,
+ BidVideo: bidVideo,
+ })
}
}
+ return bidResponse, errors
+}
- if request.Site != nil {
- siteCopy := *request.Site
- siteCopy.Publisher = &openrtb2.Publisher{ID: publisherID}
+func (adapter *adapter) makeIndividualRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) {
+ imps := request.Imp
- if request.Site.Ext != nil {
- var siteExt siteExt
- err := json.Unmarshal([]byte(request.Site.Ext), &siteExt)
+ requests := make([]*adapters.RequestData, 0, len(imps))
+ errors := make([]error, 0, len(imps))
+
+ for _, imp := range imps {
+ impsByMediaType, err := splitImpressionsByMediaType(&imp)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ for _, impByMediaType := range impsByMediaType {
+ request.Imp = []openrtb2.Imp{impByMediaType}
+ if err := prepareIndividualRequest(request); err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ requestData, err := adapter.makeRequest(request)
if err != nil {
- errs = append(errs, err)
- return nil, errs
+ errors = append(errors, err)
+ continue
}
- siteCopy.Keywords = siteExt.Data.Keywords
- siteCopy.Ext = nil
+
+ requests = append(requests, requestData)
}
- request.Site = &siteCopy
}
- if request.App != nil {
- appCopy := *request.App
- appCopy.Publisher = &openrtb2.Publisher{ID: publisherID}
+ return requests, errors
+}
- request.App = &appCopy
+func splitImpressionsByMediaType(imp *openrtb2.Imp) ([]openrtb2.Imp, error) {
+ if imp.Banner == nil && imp.Video == nil {
+ return nil, &errortypes.BadInput{Message: "Invalid MediaType. Smaato only supports Banner and Video."}
}
- if request.User != nil && request.User.Ext != nil {
- var userExt userExt
- var userExtRaw map[string]json.RawMessage
+ imps := make([]openrtb2.Imp, 0, 2)
- rawExtErr := json.Unmarshal(request.User.Ext, &userExtRaw)
- if rawExtErr != nil {
- errs = append(errs, rawExtErr)
- return nil, errs
- }
+ if imp.Banner != nil {
+ impCopy := *imp
+ impCopy.Video = nil
+ imps = append(imps, impCopy)
+ }
+
+ if imp.Video != nil {
+ imp.Banner = nil
+ imps = append(imps, *imp)
+ }
+
+ return imps, nil
+}
+
+func (adapter *adapter) makePodRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) {
+ pods, orderedKeys, errors := groupImpressionsByPod(request.Imp)
+ requests := make([]*adapters.RequestData, 0, len(pods))
- userExtErr := json.Unmarshal([]byte(request.User.Ext), &userExt)
- if userExtErr != nil {
- errs = append(errs, userExtErr)
- return nil, errs
+ for _, key := range orderedKeys {
+ request.Imp = pods[key]
+
+ if err := preparePodRequest(request); err != nil {
+ errors = append(errors, err)
+ continue
}
- userCopy := *request.User
- extractUserExtAttributes(userExt, &userCopy)
- delete(userExtRaw, "data")
- userCopy.Ext, err = json.Marshal(userExtRaw)
+ requestData, err := adapter.makeRequest(request)
if err != nil {
- errs = append(errs, err)
- return nil, errs
+ errors = append(errors, err)
+ continue
}
- request.User = &userCopy
- }
- // Setting ext client info
- type bidRequestExt struct {
- Client string `json:"client"`
- }
- request.Ext, err = json.Marshal(bidRequestExt{Client: clientVersion})
- if err != nil {
- errs = append(errs, err)
- return nil, errs
+ requests = append(requests, requestData)
}
+
+ return requests, errors
+}
+
+func (adapter *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) {
reqJSON, err := json.Marshal(request)
if err != nil {
- errs = append(errs, err)
- return nil, errs
+ return nil, err
}
- uri := a.URI
-
headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
- return []*adapters.RequestData{{
+ return &adapters.RequestData{
Method: "POST",
- Uri: uri,
+ Uri: adapter.endpoint,
Body: reqJSON,
Headers: headers,
- }}, errs
+ }, nil
}
-// MakeBids unpacks the server's response into Bids.
-func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb2.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{fmt.Errorf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)}
- }
-
- var bidResp openrtb2.BidResponse
- if err := json.Unmarshal(response.Body, &bidResp); err != nil {
- return nil, []error{err}
+func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkupType, error) {
+ if admType := adMarkupType(response.Headers.Get("X-Smt-Adtype")); admType != "" {
+ return admType, nil
+ } else if strings.HasPrefix(adMarkup, `{"image":`) {
+ return smtAdTypeImg, nil
+ } else if strings.HasPrefix(adMarkup, `{"richmedia":`) {
+ return smtAdTypeRichmedia, nil
+ } else if strings.HasPrefix(adMarkup, ` 0 {
+ primaryCategory = bid.Cat[0]
}
+
+ var bidExt bidExt
+ if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
+ return nil, &errortypes.BadServerResponse{Message: "Invalid bid.ext."}
+ }
+
+ return &openrtb_ext.ExtBidPrebidVideo{
+ Duration: bidExt.Duration,
+ PrimaryCategory: primaryCategory,
+ }, nil
}
diff --git a/adapters/smaato/smaato_test.go b/adapters/smaato/smaato_test.go
index c7c4a65017f..0012bd6158d 100644
--- a/adapters/smaato/smaato_test.go
+++ b/adapters/smaato/smaato_test.go
@@ -1,7 +1,12 @@
package smaato
import (
+ "encoding/json"
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/stretchr/testify/assert"
"testing"
+ "time"
"github.com/prebid/prebid-server/adapters/adapterstest"
"github.com/prebid/prebid-server/config"
@@ -16,5 +21,93 @@ func TestJsonSamples(t *testing.T) {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}
+ adapter, _ := bidder.(*adapter)
+ assert.NotNil(t, adapter.clock)
+ adapter.clock = &mockTime{time: time.Date(2021, 6, 25, 10, 00, 0, 0, time.UTC)}
+
adapterstest.RunJSONBidderTest(t, "smaatotest", bidder)
}
+
+func TestVideoWithCategoryAndDuration(t *testing.T) {
+ bidder := &adapter{}
+
+ mockedReq := &openrtb2.BidRequest{
+ Imp: []openrtb2.Imp{{
+ ID: "1_1",
+ Video: &openrtb2.Video{
+ W: 640,
+ H: 360,
+ MIMEs: []string{"video/mp4"},
+ MaxDuration: 60,
+ Protocols: []openrtb2.Protocol{2, 3, 5, 6},
+ },
+ Ext: json.RawMessage(
+ `{
+ "bidder": {
+ "publisherId": "12345"
+ "adbreakId": "4123456"
+ }
+ }`,
+ )},
+ },
+ }
+ mockedExtReq := &adapters.RequestData{}
+ mockedBidResponse := &openrtb2.BidResponse{
+ ID: "some-id",
+ SeatBid: []openrtb2.SeatBid{{
+ Seat: "some-seat",
+ Bid: []openrtb2.Bid{{
+ ID: "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ ImpID: "1_1",
+ Price: 0.01,
+ AdM: "",
+ Cat: []string{"IAB1"},
+ Ext: json.RawMessage(
+ `{
+ "duration": 5
+ }`,
+ ),
+ }},
+ }},
+ }
+ body, _ := json.Marshal(mockedBidResponse)
+ mockedRes := &adapters.ResponseData{
+ StatusCode: 200,
+ Body: body,
+ }
+
+ expectedBidCount := 1
+ expectedBidType := openrtb_ext.BidTypeVideo
+ expectedBidDuration := 5
+ expectedBidCategory := "IAB1"
+ expectedErrorCount := 0
+
+ bidResponse, errors := bidder.MakeBids(mockedReq, mockedExtReq, mockedRes)
+
+ if len(bidResponse.Bids) != expectedBidCount {
+ t.Errorf("should have 1 bid, bids=%v", bidResponse.Bids)
+ }
+ if bidResponse.Bids[0].BidType != expectedBidType {
+ t.Errorf("bid type should be video, bidType=%s", bidResponse.Bids[0].BidType)
+ }
+ if bidResponse.Bids[0].BidVideo.Duration != expectedBidDuration {
+ t.Errorf("video duration should be set")
+ }
+ if bidResponse.Bids[0].BidVideo.PrimaryCategory != expectedBidCategory {
+ t.Errorf("bid category should be set")
+ }
+ if bidResponse.Bids[0].Bid.Cat[0] != expectedBidCategory {
+ t.Errorf("bid category should be set")
+ }
+ if len(errors) != expectedErrorCount {
+ t.Errorf("should not have any errors, errors=%v", errors)
+ }
+}
+
+type mockTime struct {
+ time time.Time
+}
+
+func (mt *mockTime) Now() time.Time {
+ return mt.time
+}
diff --git a/adapters/smaato/smaatotest/exemplary/multiple-impressions.json b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json
new file mode 100644
index 00000000000..c30a9a6a39e
--- /dev/null
+++ b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json
@@ -0,0 +1,358 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "ext": {
+ "data": {
+ "keywords": "power tools",
+ "search": "drill",
+ "content": {
+ "userrating": 4
+ }
+ }
+ }
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ },
+ "bidfloor": 0.00123,
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ },
+ {
+ "id": "postbid_iframe",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/quicktime",
+ "video/3gpp",
+ "video/x-m4v"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "ext": {
+ "rewarded": 0
+ }
+ },
+ "bidfloor": 0.00456,
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042526",
+ "adspaceId": "130563104"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString",
+ "data": {
+ "keywords": "a,b",
+ "gender": "M",
+ "yob": 1984,
+ "geo": {
+ "country": "ca"
+ }
+ }
+ }
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "tagid": "130563103",
+ "bidfloor": 0.00123,
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "postbid_iframe",
+ "tagid": "130563104",
+ "bidfloor": 0.00456,
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "ext": {
+ "rewarded": 0
+ },
+ "mimes": [
+ "video/mp4",
+ "video/quicktime",
+ "video/3gpp",
+ "video/x-m4v"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042526"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6524",
+ "crid": "CR69382",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "postbid_iframe",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50,
+ "exp": 300
+ },
+ "type": "banner"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6524",
+ "crid": "CR69382",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "postbid_iframe",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "exp": 300
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/exemplary/multiple-media-types.json b/adapters/smaato/smaatotest/exemplary/multiple-media-types.json
new file mode 100644
index 00000000000..a7d97666778
--- /dev/null
+++ b/adapters/smaato/smaatotest/exemplary/multiple-media-types.json
@@ -0,0 +1,348 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "ext": {
+ "data": {
+ "keywords": "power tools",
+ "search": "drill",
+ "content": {
+ "userrating": 4
+ }
+ }
+ }
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ },
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/quicktime",
+ "video/3gpp",
+ "video/x-m4v"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "ext": {
+ "rewarded": 0
+ }
+ },
+ "bidfloor": 0.00123,
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString",
+ "data": {
+ "keywords": "a,b",
+ "gender": "M",
+ "yob": 1984,
+ "geo": {
+ "country": "ca"
+ }
+ }
+ }
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "tagid": "130563103",
+ "bidfloor": 0.00123,
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "tagid": "130563103",
+ "bidfloor": 0.00123,
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "ext": {
+ "rewarded": 0
+ },
+ "mimes": [
+ "video/mp4",
+ "video/quicktime",
+ "video/3gpp",
+ "video/x-m4v"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50,
+ "exp": 300
+ },
+ "type": "banner"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "exp": 300
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json
index 8194f568c28..cd29d93a34d 100644
--- a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json
+++ b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json
@@ -160,7 +160,7 @@
"keywords": "keywords"
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -215,7 +215,8 @@
"nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
"price": 0.01,
"w": 350,
- "h": 50
+ "h": 50,
+ "exp": 300
},
"type": "banner"
}
diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json
index 46722c4ff71..8ddc9a7273f 100644
--- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json
+++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json
@@ -164,7 +164,7 @@
"keywords": "keywords"
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -219,7 +219,8 @@
"nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
"price": 0.01,
"w": 350,
- "h": 50
+ "h": 50,
+ "exp": 300
},
"type": "banner"
}
diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json
index 1018dbc39ac..f0fe35ff206 100644
--- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json
+++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json
@@ -129,7 +129,7 @@
"keywords": "power tools"
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -184,7 +184,8 @@
"nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
"price": 0.01,
"w": 350,
- "h": 50
+ "h": 50,
+ "exp": 300
},
"type": "banner"
}
diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json
index 0ba4050a143..babce4f892d 100644
--- a/adapters/smaato/smaatotest/exemplary/simple-banner.json
+++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json
@@ -31,6 +31,7 @@
}
]
},
+ "bidfloor": 0.00123,
"ext": {
"bidder": {
"publisherId": "1100042525",
@@ -80,6 +81,7 @@
{
"id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
"tagid": "130563103",
+ "bidfloor": 0.00123,
"banner": {
"h": 50,
"w": 320,
@@ -125,7 +127,7 @@
"keywords": "power tools"
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -180,7 +182,8 @@
"nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
"price": 0.01,
"w": 350,
- "h": 50
+ "h": 50,
+ "exp": 300
},
"type": "banner"
}
diff --git a/adapters/smaato/smaatotest/exemplary/video-app.json b/adapters/smaato/smaatotest/exemplary/video-app.json
index bf939eb078a..fc94c2d43cb 100644
--- a/adapters/smaato/smaatotest/exemplary/video-app.json
+++ b/adapters/smaato/smaatotest/exemplary/video-app.json
@@ -165,7 +165,7 @@
"keywords": "keywords"
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -220,7 +220,8 @@
"nurl": "https://nurl",
"price": 0.01,
"w": 1024,
- "h": 768
+ "h": 768,
+ "exp": 300
},
"type": "video"
}
diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json
index bad3825bb62..205c02ad84c 100644
--- a/adapters/smaato/smaatotest/exemplary/video.json
+++ b/adapters/smaato/smaatotest/exemplary/video.json
@@ -29,6 +29,7 @@
"rewarded": 0
}
},
+ "bidfloor": 0.00123,
"ext": {
"bidder": {
"publisherId": "1100042525",
@@ -82,6 +83,7 @@
{
"id": "postbid_iframe",
"tagid": "130563103",
+ "bidfloor": 0.00123,
"video": {
"w": 1024,
"h": 768,
@@ -122,7 +124,7 @@
}
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -177,7 +179,8 @@
"nurl": "https://nurl",
"price": 0.01,
"w": 1024,
- "h": 768
+ "h": 768,
+ "exp": 300
},
"type": "video"
}
diff --git a/adapters/smaato/smaatotest/params/banner.json b/adapters/smaato/smaatotest/params/race/banner.json
similarity index 100%
rename from adapters/smaato/smaatotest/params/banner.json
rename to adapters/smaato/smaatotest/params/race/banner.json
diff --git a/adapters/smaato/smaatotest/params/race/video.json b/adapters/smaato/smaatotest/params/race/video.json
new file mode 100644
index 00000000000..a84c44d4d8e
--- /dev/null
+++ b/adapters/smaato/smaatotest/params/race/video.json
@@ -0,0 +1,4 @@
+{
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json
new file mode 100644
index 00000000000..14b966f9bdd
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json
@@ -0,0 +1,194 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "ext": {
+ "data": {
+ "keywords": "power tools",
+ "search": "drill",
+ "content": {
+ "userrating": 4
+ }
+ }
+ }
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString",
+ "data": {
+ "keywords": "a,b",
+ "gender": "M",
+ "yob": 1984,
+ "geo": {
+ "country": "ca"
+ }
+ }
+ }
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "tagid": "130563103",
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "headers": {
+ "X-Smt-Adtype": ["Img"]
+ },
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50,
+ "exp": 300
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
index db724565d52..efeba9ed6ae 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
@@ -125,7 +125,7 @@
"keywords": "power tools"
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -162,10 +162,9 @@
}
}
],
- "expectedBidResponses": [],
"expectedMakeBidsErrors": [
{
- "value": "Invalid ad markup {\"badmedia\":{\"mediadata\":{\"content\":\"hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ "value": "Invalid ad markup {\"badmedia\":{\"mediadata\":{\"content\":\"hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}.",
"comparison": "literal"
}
]
diff --git a/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json
new file mode 100644
index 00000000000..b066dc1b6cb
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json
@@ -0,0 +1,174 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "ext": {
+ "data": {
+ "keywords": "power tools",
+ "search": "drill",
+ "content": {
+ "userrating": 4
+ }
+ }
+ }
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString",
+ "data": {
+ "keywords": "a,b",
+ "gender": "M",
+ "yob": 1984,
+ "geo": {
+ "country": "ca"
+ }
+ }
+ }
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "tagid": "130563103",
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "headers": {
+ "X-Smt-Adtype": ["something"]
+ },
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unknown markup type something.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json
new file mode 100644
index 00000000000..065b639509e
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json
@@ -0,0 +1,194 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "ext": {
+ "data": {
+ "keywords": "power tools",
+ "search": "drill",
+ "content": {
+ "userrating": 4
+ }
+ }
+ }
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString",
+ "data": {
+ "keywords": "a,b",
+ "gender": "M",
+ "yob": 1984,
+ "geo": {
+ "country": "ca"
+ }
+ }
+ }
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "tagid": "130563103",
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "headers": {
+ "X-Smt-Expires": ["something"]
+ },
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50,
+ "exp": 300
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-ext-req.json b/adapters/smaato/smaatotest/supplemental/bad-ext-req.json
deleted file mode 100644
index 0c970fc5bad..00000000000
--- a/adapters/smaato/smaatotest/supplemental/bad-ext-req.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
- "mockBidRequest": {
- "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
- "site": {
- "page": "prebid.org",
- "publisher": {
- "id": "1"
- }
- },
- "imp": [
- {
- "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
- "banner": {
- "format": [
- {
- "w": 320,
- "h": 50
- },
- {
- "w": 320,
- "h": 250
- }
- ]
- },
- "ext": {
- }
- }
- ],
- "device": {
- "ua": "test-user-agent",
- "ip": "123.123.123.123",
- "language": "en",
- "dnt": 0
- },
- "user": {
- "ext": {
- "consent": "gdprConsentString"
- }
- },
- "regs": {
- "coppa": 1,
- "ext": {
- "gdpr": 1,
- "us_privacy": "uspConsentString"
- }
- }
- },
- "expectedMakeRequestsErrors": [
- {
- "value": "Key path not found",
- "comparison": "literal"
- }
- ]
-}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json
deleted file mode 100644
index 768b4ef9d2c..00000000000
--- a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "mockBidRequest": {
- "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
- "imp": [
- {
- "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
- "banner": {
- "format": []
- },
- "ext": {
- "bidder": {
- "publisherId": "1100042525",
- "adspaceId": "130563103"
- }
- }
- }
- ],
- "site": {
- "page": "prebid.org",
- "publisher": {
- "id": "1"
- }
- }
- },
- "httpCalls": [
- {
- "expectedRequest": {
- "headers": {
- "Content-Type": ["application/json;charset=utf-8"],
- "Accept": ["application/json"]
- },
- "uri": "https://prebid/bidder",
- "body": {
- "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
- "imp": [],
- "site": {
- "page": "prebid.org",
- "publisher": {
- "id": "1100042525"
- }
- },
- "ext": {
- "client": "prebid_server_0.2"
- }
- }
- }
- }
- ],
- "expectedMakeRequestsErrors": [
- {
- "value": "No sizes provided for Banner []",
- "comparison": "literal"
- }
- ],
- "expectedMakeBidsErrors": [
- {
- "value": "unexpected status code: 0. Run with request.debug = 1 for more info",
- "comparison": "literal"
- }
- ]
-}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json
new file mode 100644
index 00000000000..0c3661bdf69
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json
@@ -0,0 +1,28 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder"
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": []
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "No sizes provided for Banner.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json b/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json
new file mode 100644
index 00000000000..2b59495c829
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json
@@ -0,0 +1,27 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "page": "prebid.org"
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "native": {
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Invalid MediaType. Smaato only supports Banner and Video.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json b/adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json
new file mode 100644
index 00000000000..0df53e6c0bc
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json
@@ -0,0 +1,34 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "page": "prebid.org",
+ "ext": ""
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Invalid site.ext.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/status-code-400.json b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json
similarity index 97%
rename from adapters/smaato/smaatotest/supplemental/status-code-400.json
rename to adapters/smaato/smaatotest/supplemental/bad-status-code-response.json
index fc84c93e269..bfe3dbe2a2d 100644
--- a/adapters/smaato/smaatotest/supplemental/status-code-400.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json
@@ -125,7 +125,7 @@
"keywords": "power tools"
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -137,7 +137,7 @@
"expectedBidResponses": [],
"expectedMakeBidsErrors": [
{
- "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info.",
"comparison": "literal"
}
]
diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json b/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-request.json
similarity index 81%
rename from adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json
rename to adapters/smaato/smaatotest/supplemental/bad-user-ext-data-request.json
index 7f05b2dff14..10f5ad474c6 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-request.json
@@ -2,10 +2,7 @@
"mockBidRequest": {
"id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
"site": {
- "page": "prebid.org",
- "publisher": {
- "id": "1"
- }
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder"
},
"imp": [
{
@@ -37,8 +34,11 @@
"dnt": 0
},
"user": {
- "gender": "M",
- "ext": 99
+ "ext": {
+ "data": {
+ "yob": ""
+ }
+ }
},
"regs": {
"coppa": 1,
@@ -50,7 +50,7 @@
},
"expectedMakeRequestsErrors": [
{
- "value": "json: cannot unmarshal number into Go value of type map[string]json.RawMessage",
+ "value": "Invalid user.ext.data.",
"comparison": "literal"
}
]
diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json b/adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json
new file mode 100644
index 00000000000..26df372e14f
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json
@@ -0,0 +1,36 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "page": "prebid.org"
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ],
+ "user": {
+ "ext": 99
+ }
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Invalid user.ext.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json
new file mode 100644
index 00000000000..1d59e96d634
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json
@@ -0,0 +1,173 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "ext": {
+ "data": {
+ "keywords": "power tools",
+ "search": "drill",
+ "content": {
+ "userrating": 4
+ }
+ }
+ }
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString",
+ "data": {
+ "keywords": "a,b",
+ "gender": "M",
+ "yob": 1984,
+ "geo": {
+ "country": "ca"
+ }
+ }
+ }
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "tagid": "130563103",
+ "banner": {
+ "h": 50,
+ "w": 320
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 320,
+ "h": 50
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 320,
+ "h": 50,
+ "exp": 300
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/expires-header-response.json b/adapters/smaato/smaatotest/supplemental/expires-header-response.json
new file mode 100644
index 00000000000..be057419177
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/expires-header-response.json
@@ -0,0 +1,194 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "ext": {
+ "data": {
+ "keywords": "power tools",
+ "search": "drill",
+ "content": {
+ "userrating": 4
+ }
+ }
+ }
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString",
+ "data": {
+ "keywords": "a,b",
+ "gender": "M",
+ "yob": 1984,
+ "geo": {
+ "country": "ca"
+ }
+ }
+ }
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "tagid": "130563103",
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "headers": {
+ "X-Smt-Expires": ["1624618800000"]
+ },
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50,
+ "exp": 3600
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json b/adapters/smaato/smaatotest/supplemental/no-adspace-id-request.json
similarity index 65%
rename from adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json
rename to adapters/smaato/smaatotest/supplemental/no-adspace-id-request.json
index 9e65fce1c3e..88b0a4080e6 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json
+++ b/adapters/smaato/smaatotest/supplemental/no-adspace-id-request.json
@@ -2,9 +2,18 @@
"mockBidRequest": {
"id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
"site": {
- "page": "prebid.org",
"publisher": {
- "id": "1"
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "ext": {
+ "data": {
+ "keywords": "power tools",
+ "search": "drill",
+ "content": {
+ "userrating": 4
+ }
+ }
}
},
"imp": [
@@ -24,8 +33,7 @@
},
"ext": {
"bidder": {
- "publisherId": "1100042525",
- "adspaceId": "130563103"
+ "publisherId": "1100042525"
}
}
}
@@ -37,17 +45,16 @@
"dnt": 0
},
"user": {
- "gender": "M",
"ext": {
+ "consent": "gdprConsentString",
"data": {
- "keywords":"a,b",
+ "keywords": "a,b",
"gender": "M",
- "yob": "",
+ "yob": 1984,
"geo": {
"country": "ca"
}
- },
- "consent":"yes"
+ }
}
},
"regs": {
@@ -60,7 +67,7 @@
},
"expectedMakeRequestsErrors": [
{
- "value": "json: cannot unmarshal string into Go struct field userExtData.data.yob of type int64",
+ "value": "Missing adspaceId parameter.",
"comparison": "literal"
}
]
diff --git a/adapters/smaato/smaatotest/supplemental/no-app-site-request.json b/adapters/smaato/smaatotest/supplemental/no-app-site-request.json
new file mode 100644
index 00000000000..04a73b4f40d
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/no-app-site-request.json
@@ -0,0 +1,30 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Missing Site/App.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/status-code-204.json b/adapters/smaato/smaatotest/supplemental/no-bid-response.json
similarity index 96%
rename from adapters/smaato/smaatotest/supplemental/status-code-204.json
rename to adapters/smaato/smaatotest/supplemental/no-bid-response.json
index b409597f986..4f674f2a34d 100644
--- a/adapters/smaato/smaatotest/supplemental/status-code-204.json
+++ b/adapters/smaato/smaatotest/supplemental/no-bid-response.json
@@ -125,7 +125,7 @@
"keywords": "power tools"
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -133,7 +133,5 @@
"status": 204
}
}
- ],
- "expectedBidResponses": [],
- "expectedMakeBidsErrors": []
+ ]
}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/no-consent-info.json b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json
similarity index 98%
rename from adapters/smaato/smaatotest/supplemental/no-consent-info.json
rename to adapters/smaato/smaatotest/supplemental/no-consent-info-request.json
index b9a4294b00b..b33fea6e7e1 100644
--- a/adapters/smaato/smaatotest/supplemental/no-consent-info.json
+++ b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json
@@ -72,7 +72,7 @@
}
},
"ext": {
- "client": "prebid_server_0.2"
+ "client": "prebid_server_0.4"
}
}
},
@@ -127,7 +127,8 @@
"nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
"price": 0.01,
"w": 350,
- "h": 50
+ "h": 50,
+ "exp": 300
},
"type": "banner"
}
diff --git a/adapters/smaato/smaatotest/supplemental/no-imp-req.json b/adapters/smaato/smaatotest/supplemental/no-imp-request.json
similarity index 59%
rename from adapters/smaato/smaatotest/supplemental/no-imp-req.json
rename to adapters/smaato/smaatotest/supplemental/no-imp-request.json
index bfaf51e6ea8..eab4f2a0697 100644
--- a/adapters/smaato/smaatotest/supplemental/no-imp-req.json
+++ b/adapters/smaato/smaatotest/supplemental/no-imp-request.json
@@ -2,15 +2,12 @@
"mockBidRequest": {
"id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
"site": {
- "page": "prebid.org",
- "publisher": {
- "id": "1"
- }
+ "page": "prebid.org"
}
},
"expectedMakeRequestsErrors": [
{
- "value": "no impressions in bid request",
+ "value": "No impressions in bid request.",
"comparison": "literal"
}
]
diff --git a/adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json b/adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json
new file mode 100644
index 00000000000..60a0af594a8
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json
@@ -0,0 +1,29 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder"
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Missing publisherId parameter.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json
new file mode 100644
index 00000000000..abab70facbd
--- /dev/null
+++ b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json
@@ -0,0 +1,193 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "ext": {
+ "data": {
+ "keywords": "power tools",
+ "search": "drill",
+ "content": {
+ "userrating": 4
+ }
+ }
+ }
+ },
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adspaceId": "130563103"
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString",
+ "data": {
+ "keywords": "a,b",
+ "gender": "M",
+ "yob": 1984,
+ "geo": {
+ "country": "ca"
+ }
+ }
+ }
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "imp": [
+ {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "tagid": "130563103",
+ "banner": {
+ "h": 50,
+ "w": 320,
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ "consent": "gdprConsentString"
+ },
+ "gender": "M",
+ "keywords": "a,b",
+ "yob": 1984
+ },
+ "device": {
+ "ua": "test-user-agent",
+ "ip": "123.123.123.123",
+ "language": "en",
+ "dnt": 0
+ },
+ "regs": {
+ "coppa": 1,
+ "ext": {
+ "gdpr": 1,
+ "us_privacy": "uspConsentString"
+ }
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder",
+ "keywords": "power tools"
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "headers": {
+ "X-Smt-Expires": ["1524618800000"]
+ },
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png",
+ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D",
+ "price": 0.01,
+ "w": 350,
+ "h": 50
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/video/multiple-adpods.json b/adapters/smaato/smaatotest/video/multiple-adpods.json
new file mode 100644
index 00000000000..e5023d87b28
--- /dev/null
+++ b/adapters/smaato/smaatotest/video/multiple-adpods.json
@@ -0,0 +1,555 @@
+{
+ "mockBidRequest": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ },
+ {
+ "id": "2_1",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ },
+ {
+ "id": "2_2",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "user": {
+ "ext": {
+ "data": {}
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "tagid": "400000001",
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 1,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "tagid": "400000001",
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 2,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ }
+ },
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_2",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB2"],
+ "ext": {
+ "duration": 30
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "2_1",
+ "tagid": "400000001",
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 1,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ },
+ {
+ "id": "2_2",
+ "tagid": "400000001",
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 2,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "2_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ }
+ },
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "2_2",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB2"],
+ "ext": {
+ "duration": 30
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ },
+ "exp": 300
+ },
+ "type": "video"
+
+ },
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_2",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB2"],
+ "ext": {
+ "duration": 30
+ },
+ "exp": 300
+ },
+ "type": "video"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "2_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ },
+ "exp": 300
+ },
+ "type": "video"
+ },
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "2_2",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB2"],
+ "ext": {
+ "duration": 30
+ },
+ "exp": 300
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/video/single-adpod.json b/adapters/smaato/smaatotest/video/single-adpod.json
new file mode 100644
index 00000000000..a5f0c0590f5
--- /dev/null
+++ b/adapters/smaato/smaatotest/video/single-adpod.json
@@ -0,0 +1,297 @@
+{
+ "mockBidRequest": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "bidfloor": 0.00123,
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "bidfloor": 0.00123,
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "user": {
+ "ext": {
+ "data": {}
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "tagid": "400000001",
+ "bidfloor": 0.00123,
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 1,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "tagid": "400000001",
+ "bidfloor": 0.00123,
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 2,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ }
+ },
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_2",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB2"],
+ "ext": {
+ "duration": 30
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ },
+ "exp": 300
+ },
+ "type": "video"
+ },
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_2",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB2"],
+ "ext": {
+ "duration": 30
+ },
+ "exp": 300
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json b/adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json
new file mode 100644
index 00000000000..308648d3f64
--- /dev/null
+++ b/adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json
@@ -0,0 +1,90 @@
+{
+ "mockBidRequest": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1234567"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1234567"
+ }
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "user": {
+ "ext": {
+ "data": {}
+ }
+ }
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Missing adbreakId parameter.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json
new file mode 100644
index 00000000000..08803d1894e
--- /dev/null
+++ b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json
@@ -0,0 +1,276 @@
+{
+ "mockBidRequest": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "user": {
+ "ext": {
+ "data": {}
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "tagid": "400000001",
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 1,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "tagid": "400000001",
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 2,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ }
+ },
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_2",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB2"],
+ "ext": {
+ "duration": 30
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Invalid ad markup .",
+ "comparison": "literal"
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ },
+ "exp": 300
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json
new file mode 100644
index 00000000000..75e288362c0
--- /dev/null
+++ b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json
@@ -0,0 +1,274 @@
+{
+ "mockBidRequest": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "user": {
+ "ext": {
+ "data": {}
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "uri": "https://prebid/bidder",
+ "body": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "tagid": "400000001",
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 1,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "tagid": "400000001",
+ "video": {
+ "w": 1024,
+ "h": 768,
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "startdelay": 0,
+ "linearity": 1,
+ "maxduration": 30,
+ "skip": 1,
+ "protocols": [
+ 7
+ ],
+ "skipmin": 5,
+ "api": [
+ 7
+ ],
+ "sequence": 2,
+ "ext": {
+ "context": "adpod"
+ }
+ }
+ }
+ ],
+ "user": {
+ "ext": {
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "ext": {
+ "client": "prebid_server_0.4"
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+ "seatbid": [
+ {
+ "seat": "CM6523",
+ "bid": [
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ }
+ },
+ {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "bidderName": "smaato",
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_2",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB2"],
+ "ext": ""
+ }
+ ]
+ }
+ ],
+ "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Invalid bid.ext.",
+ "comparison": "literal"
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "adm": "",
+ "adomain": [
+ "smaato.com"
+ ],
+ "cid": "CM6523",
+ "crid": "CR69381",
+ "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+ "impid": "1_1",
+ "iurl": "https://iurl",
+ "nurl": "https://nurl",
+ "price": 0.01,
+ "w": 1024,
+ "h": 768,
+ "cat": ["IAB1"],
+ "ext": {
+ "duration": 5
+ },
+ "exp": 300
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json b/adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json
new file mode 100644
index 00000000000..1c345de029d
--- /dev/null
+++ b/adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json
@@ -0,0 +1,37 @@
+{
+ "mockBidRequest": {
+ "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+ "site": {
+ "page": "prebid.org"
+ },
+ "imp": [
+ {
+ "id": "1_1",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ },
+ {
+ "w": 320,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "publisherId": "1100042525",
+ "adbreakId": "400000001"
+ }
+ }
+ }
+ ]
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Invalid MediaType. Smaato only supports Video for AdPod.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json b/adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json
new file mode 100644
index 00000000000..1615b670e9b
--- /dev/null
+++ b/adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json
@@ -0,0 +1,90 @@
+{
+ "mockBidRequest": {
+ "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+ "imp": [
+ {
+ "id": "1_1",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "adbreakId": "400000001"
+ }
+ }
+ },
+ {
+ "id": "1_2",
+ "video": {
+ "mimes": [
+ "video/mp4",
+ "video/3gpp"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 7
+ ],
+ "w": 1024,
+ "h": 768,
+ "startdelay": 0,
+ "linearity": 1,
+ "skip": 1,
+ "skipmin": 5,
+ "api": [
+ 7
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "adbreakId": "400000001"
+ }
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1100042525"
+ },
+ "content": {
+ "title": "a-title",
+ "season": "a-season",
+ "series": "a-series",
+ "episode": 1,
+ "len": 900,
+ "livestream": 1
+ }
+ },
+ "device": {
+ "ua": "test-user-agent"
+ },
+ "user": {
+ "ext": {
+ "data": {}
+ }
+ }
+ },
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Missing publisherId parameter.",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/smilewanted/params_test.go b/adapters/smilewanted/params_test.go
new file mode 100644
index 00000000000..2ea032d6ff3
--- /dev/null
+++ b/adapters/smilewanted/params_test.go
@@ -0,0 +1,58 @@
+package smilewanted
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/smilewanted.json
+//
+// These also validate the format of the external API: request.imp[i].ext.smilewanted
+
+// TestValidParams makes sure that the smilewanted 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.BidderSmileWanted, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected SmileWanted params: %s", validParam)
+ }
+ }
+}
+
+// TestInvalidParams makes sure that the SmileWanted 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.BidderSmileWanted, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"zoneId": "zone_code"}`,
+}
+
+var invalidParams = []string{
+ `{"zoneId": 100}`,
+ `{"zoneId": true}`,
+ `{"zoneId": 123}`,
+ `{"zoneID": "1"}`,
+ ``,
+ `null`,
+ `true`,
+ `9`,
+ `1.2`,
+ `[]`,
+ `{}`,
+}
diff --git a/adapters/smilewanted/smilewanted.go b/adapters/smilewanted/smilewanted.go
new file mode 100644
index 00000000000..376389df787
--- /dev/null
+++ b/adapters/smilewanted/smilewanted.go
@@ -0,0 +1,106 @@
+package smilewanted
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+ URI string
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+
+ request.AT = 1 //Defaulting to first price auction for all prebid requests
+
+ reqJSON, err := json.Marshal(request)
+ if err != nil {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("Json not encoded. err: %s", err),
+ }}
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+ headers.Add("x-openrtb-version", "2.5")
+ headers.Add("sw-integration-type", "prebid_server")
+
+ return []*adapters.RequestData{{
+ Method: "POST",
+ Uri: a.URI,
+ Body: reqJSON,
+ Headers: headers,
+ }}, []error{}
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.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.", 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 openrtb2.BidResponse
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Bad server response: %s.", err),
+ }}
+ }
+
+ var bidReq openrtb2.BidRequest
+ if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))
+ sb := bidResp.SeatBid[0]
+ for i := 0; i < len(sb.Bid); i++ {
+ bid := sb.Bid[i]
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp),
+ })
+ }
+ return bidResponse, nil
+}
+
+// getMediaTypeForImp figures out which media type this bid is for.
+func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
+ mediaType := openrtb_ext.BidTypeBanner //default type
+ for _, imp := range imps {
+ if imp.ID == impId {
+ if imp.Video != nil {
+ mediaType = openrtb_ext.BidTypeVideo
+ }
+ return mediaType
+ }
+ }
+ return mediaType
+}
+
+// Builder builds a new instance of the SmileWanted adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+ bidder := &adapter{
+ URI: config.Endpoint,
+ }
+ return bidder, nil
+}
diff --git a/adapters/smilewanted/smilewanted_test.go b/adapters/smilewanted/smilewanted_test.go
new file mode 100644
index 00000000000..75e7849e750
--- /dev/null
+++ b/adapters/smilewanted/smilewanted_test.go
@@ -0,0 +1,20 @@
+package smilewanted
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderSmileWanted, config.Adapter{
+ Endpoint: "http://example.com"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "smilewantedtest", bidder)
+}
diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..0c68d74c588
--- /dev/null
+++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json
@@ -0,0 +1,94 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "at" : 1,
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com",
+ "body": {
+ "id": "test-request-id",
+ "at" : 1,
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "smilewanted",
+ "bid": [{
+ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "crid": "crid_10",
+ "h": 250,
+ "w": 300
+ }]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "bids": [
+ {
+ "bid": {
+ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "crid": "crid_10",
+ "h": 250,
+ "w": 300
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json
new file mode 100644
index 00000000000..b3ff9ba9edd
--- /dev/null
+++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json
@@ -0,0 +1,87 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 1024,
+ "h": 576
+ },
+ "ext":{
+ "bidder":{
+ "zoneId": "zone_code_test_video"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com",
+ "body": {
+ "id": "test-request-id",
+ "at": 1,
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "mimes": ["video/mp4"],
+ "protocols": [2, 5],
+ "w": 1024,
+ "h": 576
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_video"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "smilewanted",
+ "bid": [{
+ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "crid": "crid_10",
+ "w": 1024,
+ "h": 576
+ }]
+ }
+ ]
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "bids": [{
+ "bid": {
+ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "crid": "crid_10",
+ "w": 1024,
+ "h": 576
+ },
+ "type": "video"
+ }]
+ }
+ ]
+}
diff --git a/adapters/smilewanted/smilewantedtest/params/race/banner.json b/adapters/smilewanted/smilewantedtest/params/race/banner.json
new file mode 100644
index 00000000000..42dddd702a0
--- /dev/null
+++ b/adapters/smilewanted/smilewantedtest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+ "zoneId": "zone_code_test_display"
+}
diff --git a/adapters/smilewanted/smilewantedtest/params/race/video.json b/adapters/smilewanted/smilewantedtest/params/race/video.json
new file mode 100644
index 00000000000..64ac780ecde
--- /dev/null
+++ b/adapters/smilewanted/smilewantedtest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+ "zoneId": "zone_code_test_video"
+}
diff --git a/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json
new file mode 100644
index 00000000000..461ad9327a9
--- /dev/null
+++ b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json
@@ -0,0 +1,63 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "at" : 1,
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com",
+ "body": {
+ "id": "test-request-id",
+ "at" : 1,
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": "bad_json"
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Bad server response: json: cannot unmarshal string into Go value of type openrtb2.BidResponse.",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json
new file mode 100644
index 00000000000..0d8a432e26d
--- /dev/null
+++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json
@@ -0,0 +1,59 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "at" : 1,
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com",
+ "body": {
+ "id": "test-request-id",
+ "at" : 1,
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": []
+}
diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json
new file mode 100644
index 00000000000..bdf2caa3c01
--- /dev/null
+++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json
@@ -0,0 +1,64 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "at" : 1,
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com",
+ "body": {
+ "id": "test-request-id",
+ "at" : 1,
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400.",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json
new file mode 100644
index 00000000000..49a11e3ead3
--- /dev/null
+++ b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json
@@ -0,0 +1,64 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "at" : 1,
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://example.com",
+ "body": {
+ "id": "test-request-id",
+ "at" : 1,
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "zoneId": "zone_code_test_display"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 500
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/smilewanted/usersync.go b/adapters/smilewanted/usersync.go
new file mode 100644
index 00000000000..8f29cb845d8
--- /dev/null
+++ b/adapters/smilewanted/usersync.go
@@ -0,0 +1,12 @@
+package smilewanted
+
+import (
+ "text/template"
+
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/usersync"
+)
+
+func NewSmileWantedSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("smilewanted", temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/smilewanted/usersync_test.go b/adapters/smilewanted/usersync_test.go
new file mode 100644
index 00000000000..497e5061554
--- /dev/null
+++ b/adapters/smilewanted/usersync_test.go
@@ -0,0 +1,34 @@
+package smilewanted
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/prebid/prebid-server/privacy"
+ "github.com/prebid/prebid-server/privacy/ccpa"
+ "github.com/prebid/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSmileWantedSyncer(t *testing.T) {
+ syncURL := "//csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewSmileWantedSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "1",
+ Consent: "COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA",
+ },
+ CCPA: ccpa.Policy{
+ Consent: "1YNN",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "//csync.smilewanted.com/getuid?source=prebid-server&gdpr=1&gdpr_consent=COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA&us_privacy=1YNN&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D1%26gdpr_consent%3DCOyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA%26uid%3D%24UID", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go
index be1c2221ae5..40969d3638e 100644
--- a/adapters/sovrn/sovrn.go
+++ b/adapters/sovrn/sovrn.go
@@ -287,7 +287,10 @@ func preprocess(imp *openrtb2.Imp) (string, error) {
}
imp.TagID = getTagid(sovrnExt)
- imp.BidFloor = sovrnExt.BidFloor
+
+ if imp.BidFloor == 0 && sovrnExt.BidFloor > 0 {
+ imp.BidFloor = sovrnExt.BidFloor
+ }
return imp.TagID, nil
}
diff --git a/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json
new file mode 100644
index 00000000000..4b997b68266
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json
@@ -0,0 +1,126 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "bidfloor": 1.69,
+ "ext": {
+ "bidder": {
+ "tagid": "123456",
+ "bidfloor": 4.20
+ }
+ }
+ }
+ ],
+ "device": { },
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site"
+ },
+ "user": {
+ "buyeruid": "test_reader_id"
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json"],
+ "Cookie": ["ljt_reader=test_reader_id"]
+ },
+ "uri": "http://sovrn.com/test/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "tagid": "123456",
+ "bidfloor": 1.69,
+ "ext": {
+ "bidder": {
+ "tagid": "123456",
+ "bidfloor": 4.20
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site"
+ },
+ "user": {
+ "buyeruid": "test_reader_id"
+ },
+ "device": { }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "adm": "some-test-ad",
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "adm": "some-test-ad",
+ "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json
new file mode 100644
index 00000000000..0aa3ad74e62
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json
@@ -0,0 +1,122 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "tagid": "123456"
+ }
+ }
+ }
+ ],
+ "device": { },
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site"
+ },
+ "user": {
+ "buyeruid": "test_reader_id"
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json"],
+ "Cookie": ["ljt_reader=test_reader_id"]
+ },
+ "uri": "http://sovrn.com/test/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "tagid": "123456",
+ "ext": {
+ "bidder": {
+ "tagid": "123456"
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site"
+ },
+ "user": {
+ "buyeruid": "test_reader_id"
+ },
+ "device": { }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "adm": "some-test-ad",
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "adm": "some-test-ad",
+ "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json
new file mode 100644
index 00000000000..3cd6539f988
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json
@@ -0,0 +1,125 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "tagid": "123456",
+ "bidfloor": 4.20
+ }
+ }
+ }
+ ],
+ "device": { },
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site"
+ },
+ "user": {
+ "buyeruid": "test_reader_id"
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json"],
+ "Cookie": ["ljt_reader=test_reader_id"]
+ },
+ "uri": "http://sovrn.com/test/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "tagid": "123456",
+ "bidfloor": 4.20,
+ "ext": {
+ "bidder": {
+ "tagid": "123456",
+ "bidfloor": 4.20
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site"
+ },
+ "user": {
+ "buyeruid": "test_reader_id"
+ },
+ "device": { }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "adm": "some-test-ad",
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "adm": "some-test-ad",
+ "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json
new file mode 100644
index 00000000000..cb74e5643b6
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json
@@ -0,0 +1,124 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "bidfloor": 1.69,
+ "ext": {
+ "bidder": {
+ "tagid": "123456"
+ }
+ }
+ }
+ ],
+ "device": { },
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site"
+ },
+ "user": {
+ "buyeruid": "test_reader_id"
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "headers": {
+ "Content-Type": ["application/json"],
+ "Cookie": ["ljt_reader=test_reader_id"]
+ },
+ "uri": "http://sovrn.com/test/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "tagid": "123456",
+ "bidfloor": 1.69,
+ "ext": {
+ "bidder": {
+ "tagid": "123456"
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.publisher.com",
+ "page": "http://www.publisher.com/awesome/site"
+ },
+ "user": {
+ "buyeruid": "test_reader_id"
+ },
+ "device": { }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "adm": "some-test-ad",
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "impid": "test-imp-id",
+ "price": 3.5,
+ "adm": "some-test-ad",
+ "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/tappx/params_test.go b/adapters/tappx/params_test.go
index 3a73d4dab53..9457924875f 100644
--- a/adapters/tappx/params_test.go
+++ b/adapters/tappx/params_test.go
@@ -35,6 +35,11 @@ func TestInvalidParams(t *testing.T) {
var validParams = []string{
`{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com"}`,
`{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5}`,
+ `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "mktag":"txmk-xxxxx-xxx-xxxx"}`,
+ `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcid":["123"]}`,
+ `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcrid":["245"]}`,
+ `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcrid":["245", "321"]}`,
+ `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcid":["123", "654"], "bcrid":["245", "321"]}`,
}
var invalidParams = []string{
@@ -60,4 +65,14 @@ var invalidParams = []string{
`{"tappxkey": 1, "endpoint":"ZZ1INTERNALTEST149147915", "host":""}`,
`{"tappxkey":"pub-12345-android-9876", "endpoint": 1, "host":""}`,
`{"tappxkey": 1, "endpoint": 1, "host": 123}`,
+ `{"tappxkey": "1", "endpoint": 1}`,
+ `{"tappxkey": "1", "endpoint": "ZZ1INTERNALTEST149147915", "host":[]]}`,
+ `{"tappxkey": "1", "endpoint": 1, "host":"host"}`,
+ `{"tappxkey": "1", "endpoint": "1", "host":"host", "mktag":1}`,
+ `{"tappxkey": "1", "endpoint": "1", "host":"host", "mktag":[1,2]}`,
+ `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":""}`,
+ `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":"123", bcrid: ["123"]}`,
+ `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":["123"], bcrid: 123}`,
+ `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":["123"], bcrid: [123]}`,
+ `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":[123], bcrid: ["123"]}`,
}
diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go
index 5970ccb6cfe..5f0710cf08a 100644
--- a/adapters/tappx/tappx.go
+++ b/adapters/tappx/tappx.go
@@ -18,13 +18,24 @@ import (
"github.com/prebid/prebid-server/openrtb_ext"
)
-const TAPPX_BIDDER_VERSION = "1.2"
+const TAPPX_BIDDER_VERSION = "1.3"
const TYPE_CNN = "prebid"
type TappxAdapter struct {
endpointTemplate template.Template
}
+type Bidder struct {
+ Tappxkey string `json:"tappxkey"`
+ Mktag string `json:"mktag,omitempty"`
+ Bcid []string `json:"bcid,omitempty"`
+ Bcrid []string `json:"bcrid,omitempty"`
+}
+
+type Ext struct {
+ Bidder `json:"bidder"`
+}
+
// Builder builds a new instance of the Tappx adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
template, err := template.New("endpointTemplate").Parse(config.Endpoint)
@@ -51,7 +62,6 @@ func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt
Message: "Error parsing bidderExt object",
}}
}
-
var tappxExt openrtb_ext.ExtImpTappx
if err := json.Unmarshal(bidderExt.Bidder, &tappxExt); err != nil {
return nil, []error{&errortypes.BadInput{
@@ -59,6 +69,23 @@ func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt
}}
}
+ ext := Ext{
+ Bidder: Bidder{
+ Tappxkey: tappxExt.TappxKey,
+ Mktag: tappxExt.Mktag,
+ Bcid: tappxExt.Bcid,
+ Bcrid: tappxExt.Bcrid,
+ },
+ }
+
+ if jsonext, err := json.Marshal(ext); err == nil {
+ request.Ext = jsonext
+ } else {
+ return nil, []error{&errortypes.FailedToRequestBids{
+ Message: "Error marshaling tappxExt parameters",
+ }}
+ }
+
var test int
test = int(request.Test)
diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go
index 10e57d12132..ea7011a7bdc 100644
--- a/adapters/tappx/tappx_test.go
+++ b/adapters/tappx/tappx_test.go
@@ -47,7 +47,7 @@ func TestTsValue(t *testing.T) {
url, err := bidderTappx.buildEndpointURL(&tappxExt, test)
- match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.2`, url)
+ match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.3`, url)
if err != nil {
t.Errorf("Error while running regex validation: %s", err.Error())
return
diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json
new file mode 100644
index 00000000000..a6ddf2848e2
--- /dev/null
+++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json
@@ -0,0 +1,130 @@
+{
+ "mockBidRequest": {
+ "id": "0000000000001",
+ "test": 1,
+ "imp": [
+ {
+ "id": "adunit-1",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876",
+ "endpoint": "ZZ123456PS",
+ "host": "testing.ssp.tappx.com/rtb/v2/",
+ "mktag": "mktag-12345-android-9876",
+ "bcid": ["1","2","3"],
+ "bcrid": ["4","5","6"]
+ }
+ }
+ }
+ ],
+ "app": {
+ "id": "app_001",
+ "bundle": "com.rovio.angrybirds",
+ "publisher": {
+ "id": "2"
+ }
+ },
+ "user": {
+ "buyeruid": "A-38327932832"
+ }
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3",
+ "body": {
+ "id": "0000000000001",
+ "test": 1,
+ "imp": [
+ {
+ "id": "adunit-1",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876",
+ "endpoint": "ZZ123456PS",
+ "host": "testing.ssp.tappx.com/rtb/v2/",
+ "mktag": "mktag-12345-android-9876",
+ "bcid": ["1","2","3"],
+ "bcrid": ["4","5","6"]
+ }
+ }
+ }
+ ],
+ "app": {
+ "bundle": "com.rovio.angrybirds",
+ "id": "app_001",
+ "publisher": {
+ "id": "2"
+ }
+ },
+ "user": {
+ "buyeruid": "A-38327932832"
+ },
+ "ext": {
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876",
+ "mktag": "mktag-12345-android-9876",
+ "bcid": ["1","2","3"],
+ "bcrid": ["4","5","6"]
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4",
+ "seatbid": [{
+ "bid": [{
+ "id": "wehM-93KGr0_0_0",
+ "impid": "adunit-1",
+ "price": 0.5,
+ "cid": "3706",
+ "crid": "19005",
+ "adid": "19005",
+ "adm": "",
+ "cat": ["IAB2"],
+ "adomain": ["test.com"],
+ "h": 250,
+ "w": 300
+ }]
+ }],
+ "bidid": "wehM-93KGr0"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "wehM-93KGr0_0_0",
+ "impid": "adunit-1",
+ "price": 0.5,
+ "adm": "",
+ "adid": "19005",
+ "adomain": ["test.com"],
+ "cid": "3706",
+ "crid": "19005",
+ "w": 300,
+ "h": 250,
+ "cat": ["IAB2"]
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json
index 3c3037afefb..259d51cb34f 100644
--- a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json
+++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json
@@ -33,7 +33,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://ZZ123456PS.ssp.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
+ "uri": "http://ZZ123456PS.ssp.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3",
"body": {
"id": "0000000000001",
"test": 1,
@@ -48,7 +48,7 @@
"bidder": {
"tappxkey": "pub-12345-android-9876",
"endpoint": "ZZ123456PS",
- "host": "ZZ123456PS.ssp.tappx.com/rtb/"
+ "host": "ZZ123456PS.ssp.tappx.com/rtb/"
}
}
}
@@ -62,6 +62,11 @@
},
"user": {
"buyeruid": "A-38327932832"
+ },
+ "ext": {
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876"
+ }
}
}
},
diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json
index 54f472d9fff..532e2b1f4a1 100644
--- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json
+++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json
@@ -33,7 +33,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
+ "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3",
"body": {
"id": "0000000000001",
"test": 1,
@@ -62,6 +62,11 @@
},
"user": {
"buyeruid": "A-38327932832"
+ },
+ "ext":{
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876"
+ }
}
}
},
diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json
index 58490233ede..e8858bd6ea6 100644
--- a/adapters/tappx/tappxtest/exemplary/single-banner-site.json
+++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json
@@ -37,7 +37,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2",
+ "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.3",
"body": {
"id": "0000000000001",
"test": 1,
@@ -70,6 +70,11 @@
},
"user": {
"buyeruid": "A-38327932832"
+ },
+ "ext":{
+ "bidder": {
+ "tappxkey": "pub-12345-site-9876"
+ }
}
}
},
diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json
index d6ce0554c5f..23e079258e7 100644
--- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json
+++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json
@@ -35,7 +35,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
+ "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3",
"body": {
"id": "0000000000001",
"test": 1,
@@ -67,6 +67,11 @@
},
"user": {
"buyeruid": "A-38327932832"
+ },
+ "ext":{
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876"
+ }
}
}
},
diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json
index f151151e776..85872b6a29e 100644
--- a/adapters/tappx/tappxtest/exemplary/single-video-site.json
+++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json
@@ -39,7 +39,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2",
+ "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.3",
"body": {
"id": "0000000000001",
"test": 1,
@@ -75,6 +75,11 @@
},
"user": {
"buyeruid": "A-38327932832"
+ },
+ "ext":{
+ "bidder": {
+ "tappxkey": "pub-12345-site-9876"
+ }
}
}
},
diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json
index 1c72cc90f24..918b278e6dc 100644
--- a/adapters/tappx/tappxtest/supplemental/204status.json
+++ b/adapters/tappx/tappxtest/supplemental/204status.json
@@ -30,7 +30,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
+ "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3",
"body": {
"id": "0000000000001",
"test": 1,
@@ -56,6 +56,11 @@
"publisher": {
"id": "2"
}
+ },
+ "ext": {
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876"
+ }
}
}
},
diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json
index 093f77adfc6..3d3ced65e25 100644
--- a/adapters/tappx/tappxtest/supplemental/bidfloor.json
+++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json
@@ -34,7 +34,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
+ "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3",
"body": {
"id": "0000000000001",
"test": 1,
@@ -65,6 +65,11 @@
},
"user": {
"buyeruid": "A-38327932832"
+ },
+ "ext": {
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876"
+ }
}
}
},
diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json
index a80a5eaa675..f1783b3f77a 100644
--- a/adapters/tappx/tappxtest/supplemental/http-err-status.json
+++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json
@@ -30,7 +30,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
+ "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3",
"body": {
"id": "0000000000001",
"test": 1,
@@ -56,6 +56,11 @@
"publisher": {
"id": "2"
}
+ },
+ "ext": {
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876"
+ }
}
}
},
diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json
index 41dcc26d653..4b855c57404 100644
--- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json
+++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json
@@ -30,7 +30,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
+ "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3",
"body": {
"id": "0000000000001",
"test": 1,
@@ -56,6 +56,11 @@
"publisher": {
"id": "2"
}
+ },
+ "ext": {
+ "bidder": {
+ "tappxkey": "pub-12345-android-9876"
+ }
}
}
},
diff --git a/adapters/viewdeos/usersync.go b/adapters/viewdeos/usersync.go
new file mode 100644
index 00000000000..05cb80c54a1
--- /dev/null
+++ b/adapters/viewdeos/usersync.go
@@ -0,0 +1,12 @@
+package viewdeos
+
+import (
+ "text/template"
+
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/usersync"
+)
+
+func NewViewdeosSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("viewdeos", temp, adapters.SyncTypeIframe)
+}
diff --git a/adapters/viewdeos/usersync_test.go b/adapters/viewdeos/usersync_test.go
new file mode 100644
index 00000000000..8b8908a44e6
--- /dev/null
+++ b/adapters/viewdeos/usersync_test.go
@@ -0,0 +1,29 @@
+package viewdeos
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/prebid/prebid-server/privacy"
+ "github.com/prebid/prebid-server/privacy/gdpr"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestViewdeosSyncer(t *testing.T) {
+ syncURL := "//sync.sync.viewdeos.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D"
+ syncURLTemplate := template.Must(
+ template.New("sync-template").Parse(syncURL),
+ )
+
+ syncer := NewViewdeosSyncer(syncURLTemplate)
+ syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+ GDPR: gdpr.Policy{
+ Signal: "0",
+ },
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, "//sync.sync.viewdeos.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL)
+ assert.Equal(t, "iframe", syncInfo.Type)
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go
index 310dbe1a481..fe95a66ed6f 100644
--- a/analytics/config/config_test.go
+++ b/analytics/config/config_test.go
@@ -1,13 +1,12 @@
package config
import (
+ "github.com/stretchr/testify/assert"
"net/http"
"os"
"testing"
"github.com/mxmCherry/openrtb/v15/openrtb2"
- "github.com/stretchr/testify/assert"
-
"github.com/prebid/prebid-server/analytics"
"github.com/prebid/prebid-server/config"
)
diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go
index 0c3d3c9e6ac..45a72266569 100644
--- a/analytics/filesystem/file_module_test.go
+++ b/analytics/filesystem/file_module_test.go
@@ -1,14 +1,13 @@
package filesystem
import (
+ "github.com/prebid/prebid-server/config"
"net/http"
"os"
"strings"
"testing"
"github.com/mxmCherry/openrtb/v15/openrtb2"
- "github.com/prebid/prebid-server/config"
-
"github.com/prebid/prebid-server/analytics"
"github.com/prebid/prebid-server/usersync"
)
diff --git a/config/adapter.go b/config/adapter.go
index ff262b186fd..8cdad538dbf 100644
--- a/config/adapter.go
+++ b/config/adapter.go
@@ -64,6 +64,7 @@ const (
dummyGDPR string = "0"
dummyGDPRConsent string = "someGDPRConsentString"
dummyCCPA string = "1NYN"
+ dummyZoneID string = "zone"
)
// validateAdapterEndpoint makes sure that an adapter has a valid endpoint
@@ -84,6 +85,7 @@ func validateAdapterEndpoint(endpoint string, adapterName string, errs []error)
Host: dummyHost,
PublisherID: dummyPublisherID,
AccountID: dummyAccountID,
+ ZoneID: dummyZoneID,
})
if err != nil {
return append(errs, fmt.Errorf("Unable to resolve endpoint: %s for adapter: %s. %v", endpoint, adapterName, err))
diff --git a/config/config.go b/config/config.go
old mode 100755
new mode 100644
index 83001b10f3c..efb71adcc31
--- a/config/config.go
+++ b/config/config.go
@@ -98,7 +98,7 @@ type HTTPClient struct {
DialKeepAlive int `mapstructure:"dial_keepalive"`
}
-func (cfg *Configuration) validate() []error {
+func (cfg *Configuration) validate(v *viper.Viper) []error {
var errs []error
errs = cfg.AuctionTimeouts.validate(errs)
errs = cfg.StoredRequests.validate(errs)
@@ -110,7 +110,7 @@ func (cfg *Configuration) validate() []error {
if cfg.MaxRequestSize < 0 {
errs = append(errs, fmt.Errorf("cfg.max_request_size must be >= 0. Got %d", cfg.MaxRequestSize))
}
- errs = cfg.GDPR.validate(errs)
+ errs = cfg.GDPR.validate(v, errs)
errs = cfg.CurrencyConverter.validate(errs)
errs = validateAdapters(cfg.Adapters, errs)
errs = cfg.Debug.validate(errs)
@@ -198,22 +198,26 @@ type Privacy struct {
type GDPR struct {
Enabled bool `mapstructure:"enabled"`
HostVendorID int `mapstructure:"host_vendor_id"`
- UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"`
+ DefaultValue string `mapstructure:"default_value"`
Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"`
NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"`
NonStandardPublisherMap map[string]struct{}
- TCF1 TCF1 `mapstructure:"tcf1"`
TCF2 TCF2 `mapstructure:"tcf2"`
AMPException bool `mapstructure:"amp_exception"` // Deprecated: Use account-level GDPR settings (gdpr.integration_enabled.amp) instead
// EEACountries (EEA = European Economic Area) are a list of countries where we should assume GDPR applies.
// If the gdpr flag is unset in a request, but geo.country is set, we will assume GDPR applies if and only
// if the country matches one on this list. If both the GDPR flag and country are not set, we default
- // to UsersyncIfAmbiguous
+ // to DefaultValue
EEACountries []string `mapstructure:"eea_countries"`
EEACountriesMap map[string]struct{}
}
-func (cfg *GDPR) validate(errs []error) []error {
+func (cfg *GDPR) validate(v *viper.Viper, errs []error) []error {
+ if !v.IsSet("gdpr.default_value") {
+ errs = append(errs, fmt.Errorf("gdpr.default_value is required and must be specified"))
+ } else if cfg.DefaultValue != "0" && cfg.DefaultValue != "1" {
+ errs = append(errs, fmt.Errorf("gdpr.default_value must be 0 or 1"))
+ }
if cfg.HostVendorID < 0 || cfg.HostVendorID > 0xffff {
errs = append(errs, fmt.Errorf("gdpr.host_vendor_id must be in the range [0, %d]. Got %d", 0xffff, cfg.HostVendorID))
}
@@ -223,9 +227,6 @@ func (cfg *GDPR) validate(errs []error) []error {
if cfg.AMPException == true {
errs = append(errs, fmt.Errorf("gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)"))
}
- if cfg.TCF1.FetchGVL == true {
- errs = append(errs, fmt.Errorf("gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward"))
- }
return errs
}
@@ -242,28 +243,33 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration {
return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond
}
-// TCF1 defines the TCF1 specific configurations for GDPR
-type TCF1 struct {
- FetchGVL bool `mapstructure:"fetch_gvl"` // Deprecated: In a future version TCF1 will always use the fallback GVL
- FallbackGVLPath string `mapstructure:"fallback_gvl_path"`
-}
-
// 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"`
+ Enabled bool `mapstructure:"enabled"`
+ Purpose1 TCF2Purpose `mapstructure:"purpose1"`
+ Purpose2 TCF2Purpose `mapstructure:"purpose2"`
+ Purpose3 TCF2Purpose `mapstructure:"purpose3"`
+ Purpose4 TCF2Purpose `mapstructure:"purpose4"`
+ Purpose5 TCF2Purpose `mapstructure:"purpose5"`
+ Purpose6 TCF2Purpose `mapstructure:"purpose6"`
+ Purpose7 TCF2Purpose `mapstructure:"purpose7"`
+ Purpose8 TCF2Purpose `mapstructure:"purpose8"`
+ Purpose9 TCF2Purpose `mapstructure:"purpose9"`
+ Purpose10 TCF2Purpose `mapstructure:"purpose10"`
+ SpecialPurpose1 TCF2Purpose `mapstructure:"special_purpose1"`
+ PurposeOneTreatment TCF2PurposeOneTreatment `mapstructure:"purpose_one_treatment"`
}
// Making a purpose struct so purpose specific details can be added later.
-type PurposeDetail struct {
- Enabled bool `mapstructure:"enabled"`
+type TCF2Purpose struct {
+ Enabled bool `mapstructure:"enabled"`
+ EnforceVendors bool `mapstructure:"enforce_vendors"`
+ // Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed
+ VendorExceptions []openrtb_ext.BidderName `mapstructure:"vendor_exceptions"`
+ VendorExceptionMap map[openrtb_ext.BidderName]struct{}
}
-type PurposeOneTreatement struct {
+type TCF2PurposeOneTreatment struct {
Enabled bool `mapstructure:"enabled"`
AccessAllowed bool `mapstructure:"access_allowed"`
}
@@ -358,6 +364,9 @@ type DisabledMetrics struct {
// server establishes with bidder servers such as the number of connections
// that were created or reused.
AdapterConnectionMetrics bool `mapstructure:"adapter_connections_metrics"`
+
+ // True if we don't want to collect the per adapter GDPR request blocked metric
+ AdapterGDPRRequestBlocked bool `mapstructure:"adapter_gdpr_request_blocked"`
}
func (cfg *Metrics) validate(errs []error) []error {
@@ -451,6 +460,7 @@ type DefReqFiles struct {
type Debug struct {
TimeoutNotification TimeoutNotification `mapstructure:"timeout_notification"`
+ OverrideToken string `mapstructure:"override_token"`
}
func (cfg *Debug) validate(errs []error) []error {
@@ -509,6 +519,30 @@ func New(v *viper.Viper) (*Configuration, error) {
c.GDPR.NonStandardPublisherMap[c.GDPR.EEACountries[i]] = s
}
+ // To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table located in the
+ // VendorExceptions field of the GDPR.TCF2.PurposeX struct defined in this file
+ purposeConfigs := []*TCF2Purpose{
+ &c.GDPR.TCF2.Purpose1,
+ &c.GDPR.TCF2.Purpose2,
+ &c.GDPR.TCF2.Purpose3,
+ &c.GDPR.TCF2.Purpose4,
+ &c.GDPR.TCF2.Purpose5,
+ &c.GDPR.TCF2.Purpose6,
+ &c.GDPR.TCF2.Purpose7,
+ &c.GDPR.TCF2.Purpose8,
+ &c.GDPR.TCF2.Purpose9,
+ &c.GDPR.TCF2.Purpose10,
+ &c.GDPR.TCF2.SpecialPurpose1,
+ }
+ for c := 0; c < len(purposeConfigs); c++ {
+ purposeConfigs[c].VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{})
+
+ for v := 0; v < len(purposeConfigs[c].VendorExceptions); v++ {
+ bidderName := purposeConfigs[c].VendorExceptions[v]
+ purposeConfigs[c].VendorExceptionMap[bidderName] = struct{}{}
+ }
+ }
+
// To look for a request's app_id in O(1) time, we fill this hash table located in the
// the BlacklistedApps field of the Configuration struct defined in this file
c.BlacklistedAppMap = make(map[string]bool)
@@ -528,7 +562,7 @@ func New(v *viper.Viper) (*Configuration, error) {
glog.Info("Logging the resolved configuration:")
logGeneral(reflect.ValueOf(c), " \t")
- if errs := c.validate(); len(errs) > 0 {
+ if errs := c.validate(v); len(errs) > 0 {
return &c, errortypes.NewAggregateError("validation errors", errs)
}
@@ -573,6 +607,8 @@ func (cfg *Configuration) setDerivedDefaults() {
externalURL := cfg.ExternalURL
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="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3D33across%26uid%3D33XUSERID33X&id=zzz000000000002zzz")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAcuityAds, "https://cs.admanmedia.com/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dacuityads%26uid%3D%5BUID%5D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdagio, "https://mp.4dex.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadagio%26uid%3D%7B%7BUID%7D%7D%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26us_privacy%3D{{.USPrivacy}}")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdf, "https://cm.adform.net/cookie?redirect_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdform, "https://cm.adform.net/cookie?redirect_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%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="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D")
@@ -592,6 +628,9 @@ func (cfg *Configuration) setDerivedDefaults() {
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAvocet, "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%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="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%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="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBidmyadz, "https://cookie-sync.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&red="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbidmyadz%26uid%3D%5BUID%5D%26us_privacy%3D{{.USPrivacy}}%26gdpr_consent%3D{{.GDPRConsent}}%26gdpr%3D{{.GDPR}}")
+ // openrtb_ext.BidderBidsCube doesn't have a good default.
+ // openrtb_ext.BidderBmtm doesn't have a good default.
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderColossus, "https://sync.colossusssp.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcolossus%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConnectAd, "https://cdn.connectad.io/connectmyusers.php?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconnectad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
@@ -603,6 +642,7 @@ func (cfg *Configuration) setDerivedDefaults() {
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDmx, "https://dmx.districtm.io/s/v1/img/s/10007?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDeepintent, "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEVolution, "https://sync.e-volution.ai/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3De_volution%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%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="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
// openrtb_ext.BidderEpom doesn't have a good default.
@@ -612,14 +652,16 @@ func (cfg *Configuration) setDerivedDefaults() {
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGrid, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGumGum, "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderImprovedigital, "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BPUB_USER_ID%7D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderInMobi, "https://id5-sync.com/i/495/0.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dinmobi%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BID5UID%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=186523&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
// openrtb_ext.BidderInvibes doesn't have a good default.
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderJixie, "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderKrushmedia, "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D")
- setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLifestreet, "https://ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%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="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLogicad, "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSaLunaMedia, "https://cookie.lmgssp.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsa_lunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
+ // openrtb_ext.BidderMadvertise doesn't have a good default.
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="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
// openrtb_ext.BidderMediafuse doesn't have a good default.
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D")
@@ -627,6 +669,7 @@ func (cfg *Configuration) setDerivedDefaults() {
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNoBid, "https://ads.servenobid.com/getsync?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnobid%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOperaads, "https://t.adx.opera.com/pbs/sync?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Doperaads%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOneTag, "https://onetag-sys.com/usync/?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Donetag%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_TOKEN%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOutbrain, "https://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Doutbrain%26uid%3D__ZUID__")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
@@ -638,6 +681,7 @@ func (cfg *Configuration) setDerivedDefaults() {
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartAdserver, "https://ssbsync-global.smartadserver.com/api/sync?callerId=5&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bssb_sync_pid%5D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartRTB, "https://market-global.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&rr="+url.QueryEscape(externalURL)+"%252Fsetuid%253Fbidder%253Dsmartrtb%2526gdpr%253D{{.GDPR}}%2526gdpr_consent%253D{{.GDPRConsent}}%2526uid%253D%257BXID%257D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartyAds, "https://as.ck-ie.com/prebid.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartyads%26uid%3D%5BUID%5D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmileWanted, "https://csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSomoaudience, "https://publisher-east.mobileadtrading.com/usersync?ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%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="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
@@ -714,6 +758,10 @@ 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_connections_per_host", 0) // unlimited
+ 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)
v.SetDefault("http_client.tls_handshake_timeout", 0) //no timeout
v.SetDefault("http_client.response_header_timeout", 0) //unlimited
v.SetDefault("http_client.dial_timeout", 0) //no timeout
@@ -725,6 +773,8 @@ func SetupViper(v *viper.Viper, filename string) {
// no metrics configured by default (metrics{host|database|username|password})
v.SetDefault("metrics.disabled_metrics.account_adapter_details", false)
v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true)
+ v.SetDefault("metrics.disabled_metrics.adapter_gdpr_request_blocked", false)
+ v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true)
v.SetDefault("metrics.influxdb.host", "")
v.SetDefault("metrics.influxdb.database", "")
v.SetDefault("metrics.influxdb.username", "")
@@ -815,6 +865,8 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.33across.endpoint", "https://ssc.33across.com/api/v1/s2s")
v.SetDefault("adapters.33across.partner_id", "")
v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}")
+ v.SetDefault("adapters.adagio.endpoint", "https://mp.4dex.io/ortb2")
+ v.SetDefault("adapters.adf.endpoint", "https://adx.adform.net/adx/openrtb")
v.SetDefault("adapters.adform.endpoint", "https://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")
@@ -833,6 +885,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.adxcg.disabled", true)
v.SetDefault("adapters.adyoulike.endpoint", "https://broker.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4")
v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4")
+ v.SetDefault("adapters.algorix.endpoint", "https://xyz.svr-algorix.com/rtb/sa?sid={{.SourceId}}&token={{.AccountID}}")
v.SetDefault("adapters.amx.endpoint", "http://pbs.amxrtb.com/auction/openrtb")
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
@@ -840,11 +893,15 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.audiencenetwork.disabled", true)
v.SetDefault("adapters.audiencenetwork.endpoint", "https://an.facebook.com/placementbid.ortb")
v.SetDefault("adapters.avocet.disabled", true)
+ v.SetDefault("adapters.axonix.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.between.endpoint", "http://{{.Host}}.betweendigital.com/openrtb_bid?sspId={{.PublisherID}}")
v.SetDefault("adapters.bidmachine.endpoint", "https://{{.Host}}.bidmachine.io")
+ v.SetDefault("adapters.bidmyadz.endpoint", "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc")
+ v.SetDefault("adapters.bidscube.endpoint", "http://supply.bidscube.com/?c=o&m=rtb")
+ v.SetDefault("adapters.bmtm.endpoint", "https://one.elitebidder.com/api/pbs")
v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs")
v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb")
v.SetDefault("adapters.connectad.endpoint", "http://bidder.connectad.io/API?src=pbs")
@@ -861,23 +918,27 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1")
v.SetDefault("adapters.epom.endpoint", "https://an.epom.com/ortb")
v.SetDefault("adapters.epom.disabled", true)
+ v.SetDefault("adapters.e_volution.endpoint", "http://service.e-volution.ai/pbserver")
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", "https://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.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid")
+ v.SetDefault("adapters.interactiveoffers.endpoint", "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04")
v.SetDefault("adapters.ix.disabled", false)
v.SetDefault("adapters.ix.endpoint", "http://exchange.indexww.com/pbs?p=192919")
v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost")
+ v.SetDefault("adapters.kayzen.endpoint", "https://bids-{{.ZoneID}}.bidder.kayzen.io/?exchange={{.AccountID}}")
v.SetDefault("adapters.krushmedia.endpoint", "http://ads4.krushmedia.com/?c=rtb&m=req&key={{.AccountID}}")
v.SetDefault("adapters.invibes.endpoint", "https://{{.Host}}/bid/ServerBidAdContent")
v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server")
v.SetDefault("adapters.kubient.endpoint", "https://kssp.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.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver")
v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}")
+ v.SetDefault("adapters.sa_lunamedia.endpoint", "http://balancer.lmgssp.com/pserver")
+ v.SetDefault("adapters.madvertise.endpoint", "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}")
v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet")
v.SetDefault("adapters.mediafuse.endpoint", "http://ghb.hbmp.mediafuse.com/pbs/ortb")
v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/")
@@ -888,6 +949,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.nobid.endpoint", "https://ads.servenobid.com/ortb_adreq?tek=pbs&ver=1")
v.SetDefault("adapters.onetag.endpoint", "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}")
v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid")
+ v.SetDefault("adapters.operaads.endpoint", "https://s.adx.opera.com/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}")
v.SetDefault("adapters.orbidder.endpoint", "https://orbidder.otto.de/openrtb2")
v.SetDefault("adapters.outbrain.endpoint", "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/")
v.SetDefault("adapters.pangle.disabled", true)
@@ -906,6 +968,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.smartadserver.endpoint", "https://ssb-global.smartadserver.com")
v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}")
v.SetDefault("adapters.smartyads.endpoint", "http://{{.Host}}.smartyads.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}")
+ v.SetDefault("adapters.smilewanted.endpoint", "http://prebid-server.smilewanted.com")
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")
@@ -923,6 +986,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint")
v.SetDefault("adapters.vastbidder.endpoint", "https://test.com")
v.SetDefault("adapters.verizonmedia.disabled", true)
+ v.SetDefault("adapters.viewdeos.endpoint", "http://ghb.sync.viewdeos.com/pbs/ortb")
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=1812")
v.SetDefault("adapters.yeahmobi.endpoint", "https://{{.Host}}/prebid/bid")
@@ -941,22 +1005,47 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("analytics.pubstack.buffers.count", 100)
v.SetDefault("analytics.pubstack.buffers.timeout", "900s")
v.SetDefault("amp_timeout_adjustment_ms", 0)
+ v.BindEnv("gdpr.default_value")
v.SetDefault("gdpr.enabled", true)
v.SetDefault("gdpr.host_vendor_id", 0)
+ v.SetDefault("gdpr.default_value", "0")
v.SetDefault("gdpr.usersync_if_ambiguous", true)
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.tcf1.fetch_gvl", false)
- v.SetDefault("gdpr.tcf1.fallback_gvl_path", "/home/http/GO_SERVER/dmhbserver/static/tcf1/fallback_gvl.json")
v.SetDefault("gdpr.tcf2.enabled", true)
v.SetDefault("gdpr.tcf2.purpose1.enabled", true)
v.SetDefault("gdpr.tcf2.purpose2.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose3.enabled", true)
v.SetDefault("gdpr.tcf2.purpose4.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose5.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose6.enabled", true)
v.SetDefault("gdpr.tcf2.purpose7.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose8.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose9.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose10.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose1.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose2.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose3.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose4.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose5.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose6.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose7.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose8.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose9.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose10.enforce_vendors", true)
+ v.SetDefault("gdpr.tcf2.purpose1.vendor_exceptions", []openrtb_ext.BidderName{})
+ v.SetDefault("gdpr.tcf2.purpose2.vendor_exceptions", []openrtb_ext.BidderName{})
+ v.SetDefault("gdpr.tcf2.purpose3.vendor_exceptions", []openrtb_ext.BidderName{})
+ v.SetDefault("gdpr.tcf2.purpose4.vendor_exceptions", []openrtb_ext.BidderName{})
+ v.SetDefault("gdpr.tcf2.purpose5.vendor_exceptions", []openrtb_ext.BidderName{})
+ v.SetDefault("gdpr.tcf2.purpose6.vendor_exceptions", []openrtb_ext.BidderName{})
+ v.SetDefault("gdpr.tcf2.purpose7.vendor_exceptions", []openrtb_ext.BidderName{})
+ v.SetDefault("gdpr.tcf2.purpose8.vendor_exceptions", []openrtb_ext.BidderName{})
+ v.SetDefault("gdpr.tcf2.purpose9.vendor_exceptions", []openrtb_ext.BidderName{})
+ v.SetDefault("gdpr.tcf2.purpose10.vendor_exceptions", []openrtb_ext.BidderName{})
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.tcf2.special_purpose1.vendor_exceptions", []openrtb_ext.BidderName{})
v.SetDefault("gdpr.amp_exception", false)
v.SetDefault("gdpr.eea_countries", []string{"ALA", "AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST",
"FIN", "FRA", "GUF", "DEU", "GIB", "GRC", "GLP", "GGY", "HUN", "ISL", "IRL", "IMN", "ITA", "JEY", "LVA",
@@ -985,6 +1074,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("debug.timeout_notification.log", false)
v.SetDefault("debug.timeout_notification.sampling_rate", 0.0)
v.SetDefault("debug.timeout_notification.fail_only", false)
+ v.SetDefault("debug.override_token", "")
/* IPv4
/* Site Local: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
@@ -1010,6 +1100,10 @@ func SetupViper(v *viper.Viper, filename string) {
// Migrate config settings to maintain compatibility with old configs
migrateConfig(v)
+ migrateConfigPurposeOneTreatment(v)
+
+ v.SetDefault("gdpr.tcf2.purpose_one_treatment.enabled", true)
+ v.SetDefault("gdpr.tcf2.purpose_one_treatment.access_allowed", true)
}
func migrateConfig(v *viper.Viper) {
@@ -1025,6 +1119,18 @@ func migrateConfig(v *viper.Viper) {
}
}
+func migrateConfigPurposeOneTreatment(v *viper.Viper) {
+ if oldConfig, ok := v.Get("gdpr.tcf2.purpose_one_treatement").(map[string]interface{}); ok {
+ if v.IsSet("gdpr.tcf2.purpose_one_treatment") {
+ glog.Warning("using gdpr.tcf2.purpose_one_treatment and ignoring deprecated gdpr.tcf2.purpose_one_treatement")
+ } else {
+ glog.Warning("gdpr.tcf2.purpose_one_treatement.enabled should be changed to gdpr.tcf2.purpose_one_treatment.enabled")
+ glog.Warning("gdpr.tcf2.purpose_one_treatement.access_allowed should be changed to gdpr.tcf2.purpose_one_treatment.access_allowed")
+ v.Set("gdpr.tcf2.purpose_one_treatment", oldConfig)
+ }
+ }
+}
+
func setBidderDefaults(v *viper.Viper, bidder string) {
adapterCfgPrefix := "adapters."
v.SetDefault(adapterCfgPrefix+bidder+".endpoint", "")
diff --git a/config/config_test.go b/config/config_test.go
index 43ee8fa21df..f5fbbfa341f 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -115,11 +115,8 @@ func TestExternalCacheURLValidate(t *testing.T) {
}
}
-func TestDefaults(t *testing.T) {
- v := viper.New()
- SetupViper(v, "")
- cfg, err := New(v)
- assert.NoError(t, err, "Setting up config should work but it doesn't")
+ func TestDefaults(t *testing.T) {
+ cfg, _ := newDefaultConfig(t)
cmpInts(t, "port", cfg.Port, 8000)
cmpInts(t, "admin_port", cfg.AdminPort, 6060)
@@ -135,18 +132,128 @@ func TestDefaults(t *testing.T) {
cmpInts(t, "metrics.influxdb.collection_rate_seconds", cfg.Metrics.Influxdb.MetricSendInterval, 20)
cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, false)
cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true)
+ cmpBools(t, "adapter_gdpr_request_blocked", cfg.Metrics.Disabled.AdapterGDPRRequestBlocked, false)
cmpStrings(t, "certificates_file", cfg.PemCertsFile, "")
cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled)
cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path)
cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, true)
cmpBools(t, "generate_bid_id", cfg.GenerateBidID, false)
+
+ //Assert purpose VendorExceptionMap hash tables were built correctly
+ expectedTCF2 := TCF2{
+ Enabled: true,
+ Purpose1: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ Purpose2: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ Purpose3: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ Purpose4: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ Purpose5: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ Purpose6: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ Purpose7: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ Purpose8: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ Purpose9: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ Purpose10: TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ SpecialPurpose1: TCF2Purpose{
+ Enabled: true,
+ VendorExceptions: []openrtb_ext.BidderName{},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ },
+ PurposeOneTreatment: TCF2PurposeOneTreatment{
+ Enabled: true,
+ AccessAllowed: true,
+ },
+ }
+ assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2")
}
var fullConfig = []byte(`
gdpr:
host_vendor_id: 15
- usersync_if_ambiguous: true
+ default_value: "1"
non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"]
+ tcf2:
+ purpose1:
+ enforce_vendors: false
+ vendor_exceptions: ["foo1a", "foo1b"]
+ purpose2:
+ enabled: false
+ enforce_vendors: false
+ vendor_exceptions: ["foo2"]
+ purpose3:
+ enforce_vendors: false
+ vendor_exceptions: ["foo3"]
+ purpose4:
+ enforce_vendors: false
+ vendor_exceptions: ["foo4"]
+ purpose5:
+ enforce_vendors: false
+ vendor_exceptions: ["foo5"]
+ purpose6:
+ enforce_vendors: false
+ vendor_exceptions: ["foo6"]
+ purpose7:
+ enforce_vendors: false
+ vendor_exceptions: ["foo7"]
+ purpose8:
+ enforce_vendors: false
+ vendor_exceptions: ["foo8"]
+ purpose9:
+ enforce_vendors: false
+ vendor_exceptions: ["foo9"]
+ purpose10:
+ enforce_vendors: false
+ vendor_exceptions: ["foo10"]
+ special_purpose1:
+ vendor_exceptions: ["fooSP1"]
ccpa:
enforce: true
lmt:
@@ -197,6 +304,7 @@ metrics:
disabled_metrics:
account_adapter_details: true
adapter_connections_metrics: true
+ adapter_gdpr_request_blocked: true
datacache:
type: postgres
filename: /usr/db/db.db
@@ -348,7 +456,7 @@ func TestFullConfig(t *testing.T) {
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)
+ cmpStrings(t, "gdpr.default_value", cfg.GDPR.DefaultValue, "1")
//Assert the NonStandardPublishers was correctly unmarshalled
cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[0], "siteID")
@@ -377,6 +485,81 @@ func TestFullConfig(t *testing.T) {
cmpBools(t, "cfg.BlacklistedAppMap", cfg.BlacklistedAppMap[cfg.BlacklistedApps[i]], true)
}
+ //Assert purpose VendorExceptionMap hash tables were built correctly
+ expectedTCF2 := TCF2{
+ Enabled: true,
+ Purpose1: TCF2Purpose{
+ Enabled: true, // true by default
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo1a"), openrtb_ext.BidderName("foo1b")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo1a"): {}, openrtb_ext.BidderName("foo1b"): {}},
+ },
+ Purpose2: TCF2Purpose{
+ Enabled: false,
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo2")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo2"): {}},
+ },
+ Purpose3: TCF2Purpose{
+ Enabled: true, // true by default
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo3")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo3"): {}},
+ },
+ Purpose4: TCF2Purpose{
+ Enabled: true, // true by default
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo4")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo4"): {}},
+ },
+ Purpose5: TCF2Purpose{
+ Enabled: true, // true by default
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo5")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo5"): {}},
+ },
+ Purpose6: TCF2Purpose{
+ Enabled: true, // true by default
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo6")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo6"): {}},
+ },
+ Purpose7: TCF2Purpose{
+ Enabled: true, // true by default
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo7")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo7"): {}},
+ },
+ Purpose8: TCF2Purpose{
+ Enabled: true, // true by default
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo8")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo8"): {}},
+ },
+ Purpose9: TCF2Purpose{
+ Enabled: true, // true by default
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo9")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo9"): {}},
+ },
+ Purpose10: TCF2Purpose{
+ Enabled: true, // true by default
+ EnforceVendors: false,
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}},
+ },
+ SpecialPurpose1: TCF2Purpose{
+ Enabled: true, // true by default
+ VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("fooSP1")},
+ VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("fooSP1"): {}},
+ },
+ PurposeOneTreatment: TCF2PurposeOneTreatment{
+ Enabled: true, // true by default
+ AccessAllowed: true, // true by default
+ },
+ }
+ assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2")
+
cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://currency.prebid.org")
cmpInts(t, "currency_converter.fetch_interval_seconds", cfg.CurrencyConverter.FetchIntervalSeconds, 1800)
cmpStrings(t, "recaptcha_secret", cfg.RecaptchaSecret, "asdfasdfasdfasdf")
@@ -413,16 +596,19 @@ func TestFullConfig(t *testing.T) {
cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, false)
cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true)
cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true)
+ cmpBools(t, "adapter_gdpr_request_blocked", cfg.Metrics.Disabled.AdapterGDPRRequestBlocked, true)
cmpStrings(t, "certificates_file", cfg.PemCertsFile, "/etc/ssl/cert.pem")
cmpStrings(t, "request_validation.ipv4_private_networks", cfg.RequestValidation.IPv4PrivateNetworks[0], "1.1.1.0/24")
cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16")
cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[1], "2222::/16")
cmpBools(t, "generate_bid_id", cfg.GenerateBidID, true)
+ cmpStrings(t, "debug.override_token", cfg.Debug.OverrideToken, "")
}
func TestUnmarshalAdapterExtraInfo(t *testing.T) {
v := viper.New()
SetupViper(v, "")
+ v.Set("gdpr.default_value", "0")
v.SetConfigType("yaml")
v.ReadConfig(bytes.NewBuffer(adapterExtraInfoConfig))
cfg, err := New(v)
@@ -454,6 +640,9 @@ func TestUnmarshalAdapterExtraInfo(t *testing.T) {
func TestValidConfig(t *testing.T) {
cfg := Configuration{
+ GDPR: GDPR{
+ DefaultValue: "1",
+ },
StoredRequests: StoredRequests{
Files: FileFetcherConfig{Enabled: true},
InMemoryCache: InMemoryCache{
@@ -475,14 +664,18 @@ func TestValidConfig(t *testing.T) {
},
}
+ v := viper.New()
+ v.Set("gdpr.default_value", "0")
+
resolvedStoredRequestsConfig(&cfg)
- err := cfg.validate()
+ err := cfg.validate(v)
assert.Nil(t, err, "OpenRTB filesystem config should work. %v", err)
}
func TestMigrateConfig(t *testing.T) {
v := viper.New()
SetupViper(v, "")
+ v.Set("gdpr.default_value", "0")
v.SetConfigType("yaml")
v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig))
migrateConfig(v)
@@ -499,16 +692,87 @@ func TestMigrateConfigFromEnv(t *testing.T) {
defer os.Unsetenv("PBS_STORED_REQUESTS_FILESYSTEM")
}
os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", "true")
- v := viper.New()
- SetupViper(v, "")
- cfg, err := New(v)
- assert.NoError(t, err, "Setting up config should work but it doesn't")
+ cfg, _ := newDefaultConfig(t)
cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled)
}
+func TestMigrateConfigPurposeOneTreatment(t *testing.T) {
+ oldPurposeOneTreatmentConfig := []byte(`
+ gdpr:
+ tcf2:
+ purpose_one_treatement:
+ enabled: true
+ access_allowed: true
+ `)
+ newPurposeOneTreatmentConfig := []byte(`
+ gdpr:
+ tcf2:
+ purpose_one_treatment:
+ enabled: true
+ access_allowed: true
+ `)
+ oldAndNewPurposeOneTreatmentConfig := []byte(`
+ gdpr:
+ tcf2:
+ purpose_one_treatement:
+ enabled: false
+ access_allowed: true
+ purpose_one_treatment:
+ enabled: true
+ access_allowed: false
+ `)
+
+ tests := []struct {
+ description string
+ config []byte
+ wantPurpose1TreatmentEnabled bool
+ wantPurpose1TreatmentAccessAllowed bool
+ }{
+ {
+ description: "New config and old config not set",
+ config: []byte{},
+ },
+ {
+ description: "New config not set, old config set",
+ config: oldPurposeOneTreatmentConfig,
+ wantPurpose1TreatmentEnabled: true,
+ wantPurpose1TreatmentAccessAllowed: true,
+ },
+ {
+ description: "New config set, old config not set",
+ config: newPurposeOneTreatmentConfig,
+ wantPurpose1TreatmentEnabled: true,
+ wantPurpose1TreatmentAccessAllowed: true,
+ },
+ {
+ description: "New config and old config set",
+ config: oldAndNewPurposeOneTreatmentConfig,
+ wantPurpose1TreatmentEnabled: true,
+ wantPurpose1TreatmentAccessAllowed: false,
+ },
+ }
+
+ for _, tt := range tests {
+ v := viper.New()
+ v.SetConfigType("yaml")
+ v.ReadConfig(bytes.NewBuffer(tt.config))
+
+ migrateConfigPurposeOneTreatment(v)
+
+ if len(tt.config) > 0 {
+ assert.Equal(t, tt.wantPurpose1TreatmentEnabled, v.Get("gdpr.tcf2.purpose_one_treatment.enabled").(bool), tt.description)
+ assert.Equal(t, tt.wantPurpose1TreatmentAccessAllowed, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed").(bool), tt.description)
+ } else {
+ assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.enabled"), tt.description)
+ assert.Nil(t, v.Get("gdpr.tcf2.purpose_one_treatment.access_allowed"), tt.description)
+ }
+ }
+}
+
func TestInvalidAdapterEndpointConfig(t *testing.T) {
v := viper.New()
SetupViper(v, "")
+ v.Set("gdpr.default_value", "0")
v.SetConfigType("yaml")
v.ReadConfig(bytes.NewBuffer(invalidAdapterEndpointConfig))
_, err := New(v)
@@ -518,6 +782,7 @@ func TestInvalidAdapterEndpointConfig(t *testing.T) {
func TestInvalidAdapterUserSyncURLConfig(t *testing.T) {
v := viper.New()
SetupViper(v, "")
+ v.Set("gdpr.default_value", "0")
v.SetConfigType("yaml")
v.ReadConfig(bytes.NewBuffer(invalidUserSyncURLConfig))
_, err := New(v)
@@ -525,16 +790,16 @@ func TestInvalidAdapterUserSyncURLConfig(t *testing.T) {
}
func TestNegativeRequestSize(t *testing.T) {
- cfg := newDefaultConfig(t)
+ cfg, v := newDefaultConfig(t)
cfg.MaxRequestSize = -1
- assertOneError(t, cfg.validate(), "cfg.max_request_size must be >= 0. Got -1")
+ assertOneError(t, cfg.validate(v), "cfg.max_request_size must be >= 0. Got -1")
}
func TestNegativePrometheusTimeout(t *testing.T) {
- cfg := newDefaultConfig(t)
+ cfg, v := newDefaultConfig(t)
cfg.Metrics.Prometheus.Port = 8001
cfg.Metrics.Prometheus.TimeoutMillisRaw = 0
- assertOneError(t, cfg.validate(), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001")
+ assertOneError(t, cfg.validate(v), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001")
}
func TestInvalidHostVendorID(t *testing.T) {
@@ -556,44 +821,57 @@ func TestInvalidHostVendorID(t *testing.T) {
}
for _, tt := range tests {
- cfg := newDefaultConfig(t)
+ cfg, v := newDefaultConfig(t)
cfg.GDPR.HostVendorID = tt.vendorID
- errs := cfg.validate()
+ errs := cfg.validate(v)
assert.Equal(t, 1, len(errs), tt.description)
assert.EqualError(t, errs[0], tt.wantErrorMsg, tt.description)
}
}
-func TestInvalidFetchGVL(t *testing.T) {
- cfg := newDefaultConfig(t)
- cfg.GDPR.TCF1.FetchGVL = true
- assertOneError(t, cfg.validate(), "gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward")
-}
-
func TestInvalidAMPException(t *testing.T) {
- cfg := newDefaultConfig(t)
+ cfg, v := newDefaultConfig(t)
cfg.GDPR.AMPException = true
- assertOneError(t, cfg.validate(), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)")
+ assertOneError(t, cfg.validate(v), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)")
+}
+
+func TestInvalidGDPRDefaultValue(t *testing.T) {
+ cfg, v := newDefaultConfig(t)
+ cfg.GDPR.DefaultValue = "2"
+ assertOneError(t, cfg.validate(v), "gdpr.default_value must be 0 or 1")
+}
+
+func TestMissingGDPRDefaultValue(t *testing.T) {
+ v := viper.New()
+
+ cfg, _ := newDefaultConfig(t)
+ assertOneError(t, cfg.validate(v), "gdpr.default_value is required and must be specified")
}
func TestNegativeCurrencyConverterFetchInterval(t *testing.T) {
+ v := viper.New()
+ v.Set("gdpr.default_value", "0")
+
cfg := Configuration{
CurrencyConverter: CurrencyConverter{
FetchIntervalSeconds: -1,
},
}
- err := cfg.validate()
+ err := cfg.validate(v)
assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds should prevent negative values, but it doesn't")
}
func TestOverflowedCurrencyConverterFetchInterval(t *testing.T) {
+ v := viper.New()
+ v.Set("gdpr.default_value", "0")
+
cfg := Configuration{
CurrencyConverter: CurrencyConverter{
FetchIntervalSeconds: (0xffff) + 1,
},
}
- err := cfg.validate()
+ err := cfg.validate(v)
assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds prevent values over %d, but it doesn't", 0xffff)
}
@@ -651,6 +929,7 @@ func TestNewCallsRequestValidation(t *testing.T) {
for _, test := range testCases {
v := viper.New()
SetupViper(v, "")
+ v.Set("gdpr.default_value", "0")
v.SetConfigType("yaml")
v.ReadConfig(bytes.NewBuffer([]byte(
`request_validation:
@@ -668,31 +947,32 @@ func TestNewCallsRequestValidation(t *testing.T) {
}
func TestValidateDebug(t *testing.T) {
- cfg := newDefaultConfig(t)
+ cfg, v := newDefaultConfig(t)
cfg.Debug.TimeoutNotification.SamplingRate = 1.1
- err := cfg.validate()
+ err := cfg.validate(v)
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 TestValidateAccountsConfigRestrictions(t *testing.T) {
- cfg := newDefaultConfig(t)
+ cfg, v := newDefaultConfig(t)
cfg.Accounts.Files.Enabled = true
cfg.Accounts.HTTP.Endpoint = "http://localhost"
cfg.Accounts.Postgres.ConnectionInfo.Database = "accounts"
- errs := cfg.validate()
+ errs := cfg.validate(v)
assert.Len(t, errs, 1)
assert.Contains(t, errs, errors.New("accounts.postgres: retrieving accounts via postgres not available, use accounts.files"))
}
-func newDefaultConfig(t *testing.T) *Configuration {
+func newDefaultConfig(t *testing.T) (*Configuration, *viper.Viper) {
v := viper.New()
SetupViper(v, "")
+ v.Set("gdpr.default_value", "0")
v.SetConfigType("yaml")
cfg, err := New(v)
- assert.NoError(t, err)
- return cfg
+ assert.NoError(t, err, "Setting up config should work but it doesn't")
+ return cfg, v
}
func assertOneError(t *testing.T, errs []error, message string) {
diff --git a/currency/aggregate_conversions.go b/currency/aggregate_conversions.go
new file mode 100644
index 00000000000..a15404fe501
--- /dev/null
+++ b/currency/aggregate_conversions.go
@@ -0,0 +1,41 @@
+package currency
+
+// AggregateConversions contains both the request-defined currency rate
+// map found in request.ext.prebid.currency and the currencies conversion
+// rates fetched with the RateConverter object defined in rate_converter.go
+// It implements the Conversions interface.
+type AggregateConversions struct {
+ customRates, serverRates Conversions
+}
+
+// NewAggregateConversions expects both customRates and pbsRates to not be nil
+func NewAggregateConversions(customRates, pbsRates Conversions) *AggregateConversions {
+ return &AggregateConversions{
+ customRates: customRates,
+ serverRates: pbsRates,
+ }
+}
+
+// GetRate returns the conversion rate between two currencies prioritizing
+// the customRates currency rate over that of the PBS currency rate service
+// returns an error if both Conversions objects return error.
+func (re *AggregateConversions) GetRate(from string, to string) (float64, error) {
+ rate, err := re.customRates.GetRate(from, to)
+ if err == nil {
+ return rate, nil
+ } else if _, isMissingRateErr := err.(ConversionNotFoundError); !isMissingRateErr {
+ // other error, return the error
+ return 0, err
+ }
+
+ // because the custom rates' GetRate() call returned an error other than "conversion
+ // rate not found", there's nothing wrong with the 3 letter currency code so let's
+ // try the PBS rates instead
+ return re.serverRates.GetRate(from, to)
+}
+
+// GetRates is not implemented for AggregateConversions . There is no need to call
+// this function for this scenario.
+func (r *AggregateConversions) GetRates() *map[string]map[string]float64 {
+ return nil
+}
diff --git a/currency/aggregate_conversions_test.go b/currency/aggregate_conversions_test.go
new file mode 100644
index 00000000000..773a596c28c
--- /dev/null
+++ b/currency/aggregate_conversions_test.go
@@ -0,0 +1,89 @@
+package currency
+
+import (
+ "errors"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGroupedGetRate(t *testing.T) {
+
+ // Setup:
+ customRates := NewRates(time.Now(), map[string]map[string]float64{
+ "USD": {
+ "GBP": 3.00,
+ "EUR": 2.00,
+ },
+ })
+
+ pbsRates := NewRates(time.Now(), map[string]map[string]float64{
+ "USD": {
+ "GBP": 4.00,
+ "MXN": 10.00,
+ },
+ })
+ aggregateConversions := NewAggregateConversions(customRates, pbsRates)
+
+ // Test cases:
+ type aTest struct {
+ desc string
+ from string
+ to string
+ expectedRate float64
+ }
+
+ testGroups := []struct {
+ expectedError error
+ testCases []aTest
+ }{
+ {
+ expectedError: nil,
+ testCases: []aTest{
+ {"Found in both, return custom rate", "USD", "GBP", 3.00},
+ {"Found in both, return inverse custom rate", "GBP", "USD", 1 / 3.00},
+ {"Found in custom rates only", "USD", "EUR", 2.00},
+ {"Found in PBS rates only", "USD", "MXN", 10.00},
+ {"Found in PBS rates only, return inverse", "MXN", "USD", 1 / 10.00},
+ {"Same currency, return unitary rate", "USD", "USD", 1},
+ },
+ },
+ {
+ expectedError: errors.New("currency: tag is not well-formed"),
+ testCases: []aTest{
+ {"From-currency three-digit code malformed", "XX", "EUR", 0},
+ {"To-currency three-digit code malformed", "GBP", "", 0},
+ {"Both currencies malformed", "", "", 0},
+ },
+ },
+ {
+ expectedError: errors.New("currency: tag is not a recognized currency"),
+ testCases: []aTest{
+ {"From-currency three-digit code not found", "FOO", "EUR", 0},
+ {"To-currency three-digit code not found", "GBP", "BAR", 0},
+ },
+ },
+ {
+ expectedError: ConversionNotFoundError{FromCur: "GBP", ToCur: "EUR"},
+ testCases: []aTest{
+ {"Valid three-digit currency codes, but conversion rate not found", "GBP", "EUR", 0},
+ },
+ },
+ }
+
+ for _, group := range testGroups {
+ for _, tc := range group.testCases {
+ // Execute:
+ rate, err := aggregateConversions.GetRate(tc.from, tc.to)
+
+ // Verify:
+ assert.Equal(t, tc.expectedRate, rate, "conversion rate doesn't match the expected rate: %s\n", tc.desc)
+ if group.expectedError != nil {
+ assert.Error(t, err, "error doesn't match expected: %s\n", tc.desc)
+ } else {
+ assert.NoError(t, err, "err should be nil: %s\n", tc.desc)
+ }
+ }
+ }
+}
diff --git a/currency/constant_rates.go b/currency/constant_rates.go
index 26471a966a5..ccc5c24d3fc 100644
--- a/currency/constant_rates.go
+++ b/currency/constant_rates.go
@@ -1,8 +1,6 @@
package currency
import (
- "fmt"
-
"golang.org/x/text/currency"
)
@@ -29,7 +27,7 @@ func (r *ConstantRates) GetRate(from string, to string) (float64, error) {
}
if fromUnit.String() != toUnit.String() {
- return 0, fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert '%s' => '%s'", fromUnit.String(), toUnit.String())
+ return 0, ConversionNotFoundError{FromCur: fromUnit.String(), ToCur: toUnit.String()}
}
return 1, nil
diff --git a/currency/errors.go b/currency/errors.go
new file mode 100644
index 00000000000..bb4c42aa90a
--- /dev/null
+++ b/currency/errors.go
@@ -0,0 +1,13 @@
+package currency
+
+import "fmt"
+
+// ConversionNotFoundError is thrown by the currency.Conversions GetRate(from string, to string) method
+// when the conversion rate between the two currencies, nor its reciprocal, can be found.
+type ConversionNotFoundError struct {
+ FromCur, ToCur string
+}
+
+func (err ConversionNotFoundError) Error() string {
+ return fmt.Sprintf("Currency conversion rate not found: '%s' => '%s'", err.FromCur, err.ToCur)
+}
diff --git a/currency/rates.go b/currency/rates.go
index a3ae5f30fd5..b9cb0201b38 100644
--- a/currency/rates.go
+++ b/currency/rates.go
@@ -3,7 +3,6 @@ package currency
import (
"encoding/json"
"errors"
- "fmt"
"time"
"golang.org/x/text/currency"
@@ -45,9 +44,12 @@ func (r *Rates) UnmarshalJSON(b []byte) error {
return nil
}
-// GetRate returns the conversion rate between two currencies
-// returns an error in case the conversion rate between the two given currencies is not in the currencies rates map
-func (r *Rates) GetRate(from string, to string) (float64, error) {
+// GetRate returns the conversion rate between two currencies or:
+// - An error if one of the currency strings is not well-formed
+// - An error if any of the currency strings is not a recognized currency code.
+// - A ConversionNotFoundError in case the conversion rate between the two
+// given currencies is not in the currencies rates map
+func (r *Rates) GetRate(from, to string) (float64, error) {
var err error
fromUnit, err := currency.ParseISO(from)
if err != nil {
@@ -63,12 +65,12 @@ func (r *Rates) GetRate(from string, to string) (float64, error) {
if r.Conversions != nil {
if conversion, present := r.Conversions[fromUnit.String()][toUnit.String()]; present {
// In case we have an entry FROM -> TO
- return conversion, err
+ return conversion, nil
} else if conversion, present := r.Conversions[toUnit.String()][fromUnit.String()]; present {
// In case we have an entry TO -> FROM
- return 1 / conversion, err
+ return 1 / conversion, nil
}
- return 0, fmt.Errorf("Currency conversion rate not found: '%s' => '%s'", fromUnit.String(), toUnit.String())
+ return 0, ConversionNotFoundError{FromCur: fromUnit.String(), ToCur: toUnit.String()}
}
return 0, errors.New("rates are nil")
}
diff --git a/docs/developers/images/img_grafana.png b/docs/developers/images/img_grafana.png
new file mode 100644
index 00000000000..59d6773c94a
Binary files /dev/null and b/docs/developers/images/img_grafana.png differ
diff --git a/docs/developers/metrics-configuration.md b/docs/developers/metrics-configuration.md
new file mode 100644
index 00000000000..c3cbd7a51b8
--- /dev/null
+++ b/docs/developers/metrics-configuration.md
@@ -0,0 +1,79 @@
+## Metrics configuration and how to import metrics of [Prebid server](https://docs.prebid.org/prebid-server/versions/pbs-versions-go.html) to [Grafana](https://grafana.com/) through [Prometheus](https://prometheus.io/) stack.
+
+#### These are the parameters that are capable for metrics collection
+
+- PBS_METRICS_PROMETHEUS_PORT=9100 - default is 0.
+
+If PBS_METRICS_PROMETHEUS_PORT is null, your [Prebid server](https://docs.prebid.org/prebid-server/versions/pbs-versions-go.html) won't collect any metrics.
+
+- PBS_METRICS_PROMETHEUS_NAMESPACE=prebid - default is empty.
+
+PBS_METRICS_PROMETHEUS_NAMESPACE - this is responsable for the primary prefix added to your metrics to ensure uniqueness within your cluster.
+
+- PBS_METRICS_PROMETHEUS_SUBSYSTEM=server - default is empty.
+
+PBS_METRICS_PROMETHEUS_SUBSYSTEM - this is a secondary prefix added to metrics to ensure uniqueness.
+
+- PBS_METRICS_DISABLED_METRICS_ADAPTER_CONNECTIONS_METRICS=false - default is true.
+
+PBS_METRICS_DISABLED_METRICS_ADAPTER_CONNECTIONS_METRICS - If this flag is set to true you won't get any bidder http connection adapter metrics (e.g. number of new vs reused connections) but you'll still get other adapter metrics.
+
+#### If you're going to get metrics though [Prometheus](https://prometheus.io/) and [Prometheus](https://prometheus.io/) stack has been already installed, you have several options, please chose one:
+
+- change environments into code (bad way).
+
+- create pbs.yaml if it hasn't been already created (It works, but not flexible).
+
+- input the parameters into global environments (Highly recommend you to use this way also it's reliable).
+
+#### Finally, when you fill in your credentials of metrics, please, run:
+
+ - build your app
+```bash
+go build .
+```
+ - run the server
+```bash
+./prebid-server
+```
+
+#### Make sure that an application returns any metrics - `http://localhost:9100/`, if not, please check your names of environment and recompile again.
+
+#### Add your host and port into prometheus.yml file on the instance of your [Prometheus](https://prometheus.io/).
+
+```yaml
+global:
+ scrape_interval: 15s
+ evaluation_interval: 15s
+
+ external_labels:
+ monitor: 'docker-host-alpha'
+
+rule_files:
+ - "alert.rules"
+
+scrape_configs:
+ - job_name: 'prometheus'
+ scrape_interval: 10s
+ static_configs:
+ - targets: [ 'prometheus-host:9090']
+
+ - job_name: 'prebidserver-metrics'
+ scrape_interval: 10s
+ static_configs:
+ - targets: ['prebidserver-host:9100' ]
+alerting:
+ alertmanagers:
+ - scheme: http
+ static_configs:
+ - targets:
+ - 'localhost:9093'
+```
+
+#### Import data source as [Prometheus](https://prometheus.io/) into [Grafana](https://grafana.com/)
+
+Just fill in host and port of [Prometheus](https://prometheus.io/)
+
+![img_grafana.png](images/img_grafana.png)
+
+#### In that case [Prebid server](https://docs.prebid.org/prebid-server/versions/pbs-versions-go.html) uses [package](https://github.com/prometheus/client_golang) in our case it works as [Node exporter](https://github.com/prometheus/node_exporter). Therefore, here is described only how to connect [Prebid server](https://docs.prebid.org/prebid-server/versions/pbs-versions-go.html) connection with [Prometheus](https://prometheus.io/). Also, if you are interested in [Prometheus](https://prometheus.io/) and want to dig deep, follow [docs](https://prometheus.io/docs/introduction/overview/).
diff --git a/endpoints/auction.go b/endpoints/auction.go
index 069abfec4e5..f76f8237660 100644
--- a/endpoints/auction.go
+++ b/endpoints/auction.go
@@ -146,6 +146,8 @@ func (a *auction) auction(w http.ResponseWriter, r *http.Request, _ httprouter.P
go bidderRunner(bidder, blabels)
+ } else if bidder.BidderCode == "lifestreet" {
+ bidder.Error = "Bidder is no longer available"
} else {
bidder.Error = "Unsupported bidder"
}
diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go
index 17ed7f74f45..bdf68db5be7 100644
--- a/endpoints/auction_test.go
+++ b/endpoints/auction_test.go
@@ -346,6 +346,7 @@ func TestCacheVideoOnly(t *testing.T) {
ctx := context.TODO()
v := viper.New()
config.SetupViper(v, "")
+ v.Set("gdpr.default_value", "0")
cfg, err := config.New(v)
if err != nil {
t.Fatal(err.Error())
@@ -441,9 +442,9 @@ func TestShouldUsersync(t *testing.T) {
type auctionMockPermissions struct {
allowBidderSync bool
allowHostCookies bool
- allowPI bool
- allowGeo bool
- allowID bool
+ allowBidRequest bool
+ passGeo bool
+ passID bool
}
func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) {
@@ -454,8 +455,8 @@ func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder o
return m.allowBidderSync, nil
}
-func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) {
- return m.allowPI, m.allowGeo, m.allowID, nil
+func (m *auctionMockPermissions) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) {
+ return m.allowBidRequest, m.passGeo, m.passID, nil
}
func TestBidSizeValidate(t *testing.T) {
diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go
index 0396ee9f107..447ac2385a9 100644
--- a/endpoints/cookie_sync.go
+++ b/endpoints/cookie_sync.go
@@ -97,7 +97,7 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h
}
parsedReq := &cookieSyncRequest{}
- if err := parseRequest(parsedReq, bodyBytes, deps.gDPR.UsersyncIfAmbiguous); err != nil {
+ if err := parseRequest(parsedReq, bodyBytes, deps.gDPR.DefaultValue); err != nil {
co.Status = http.StatusBadRequest
co.Errors = append(co.Errors, err)
http.Error(w, co.Errors[len(co.Errors)-1].Error(), co.Status)
@@ -188,7 +188,7 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h
enc.Encode(csResp)
}
-func parseRequest(parsedReq *cookieSyncRequest, bodyBytes []byte, usersyncIfAmbiguous bool) error {
+func parseRequest(parsedReq *cookieSyncRequest, bodyBytes []byte, gdprDefaultValue string) error {
if err := json.Unmarshal(bodyBytes, parsedReq); err != nil {
return fmt.Errorf("JSON parsing failed: %s", err.Error())
}
@@ -200,7 +200,7 @@ func parseRequest(parsedReq *cookieSyncRequest, bodyBytes []byte, usersyncIfAmbi
if parsedReq.GDPR == nil {
var gdpr = new(int)
*gdpr = 1
- if usersyncIfAmbiguous {
+ if gdprDefaultValue == "0" {
*gdpr = 0
}
parsedReq.GDPR = gdpr
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index d4b89a15118..2f56c262979 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -13,7 +13,6 @@ import (
"github.com/julienschmidt/httprouter"
"github.com/prebid/prebid-server/adapters/appnexus"
"github.com/prebid/prebid-server/adapters/audienceNetwork"
- "github.com/prebid/prebid-server/adapters/lifestreet"
"github.com/prebid/prebid-server/adapters/pubmatic"
analyticsConf "github.com/prebid/prebid-server/analytics/config"
"github.com/prebid/prebid-server/config"
@@ -41,12 +40,12 @@ func TestGDPRPreventsCookie(t *testing.T) {
}
func TestGDPRPreventsBidders(t *testing.T) {
- rr := doPost(`{"gdpr":1,"bidders":["appnexus", "pubmatic", "lifestreet"],"gdpr_consent":"BOONs2HOONs2HABABBENAGgAAAAPrABACGA"}`, nil, true, map[openrtb_ext.BidderName]usersync.Usersyncer{
- openrtb_ext.BidderLifestreet: lifestreet.NewLifestreetSyncer(template.Must(template.New("sync").Parse("someurl.com"))),
+ rr := doPost(`{"gdpr":1,"bidders":["appnexus", "pubmatic"],"gdpr_consent":"BOONs2HOONs2HABABBENAGgAAAAPrABACGA"}`, nil, true, map[openrtb_ext.BidderName]usersync.Usersyncer{
+ openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticSyncer(template.Must(template.New("sync").Parse("someurl.com"))),
})
assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8")
assert.Equal(t, http.StatusOK, rr.Code)
- assert.ElementsMatch(t, []string{"lifestreet"}, parseSyncs(t, rr.Body.Bytes()))
+ assert.ElementsMatch(t, []string{"pubmatic"}, parseSyncs(t, rr.Body.Bytes()))
assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes()))
}
@@ -111,7 +110,7 @@ func TestCCPA(t *testing.T) {
}
for _, test := range testCases {
- gdpr := config.GDPR{UsersyncIfAmbiguous: true}
+ gdpr := config.GDPR{DefaultValue: "0"}
ccpa := config.CCPA{Enforce: test.enforceCCPA}
rr := doConfigurablePost(test.requestBody, nil, true, syncersForTest(), gdpr, ccpa)
assert.Equal(t, http.StatusOK, rr.Code, test.description+":httpResponseCode")
@@ -145,12 +144,12 @@ func TestCookieSyncNoBidders(t *testing.T) {
rr := doPost("{}", nil, true, syncersForTest())
assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8")
assert.Equal(t, http.StatusOK, rr.Code)
- assert.ElementsMatch(t, []string{"appnexus", "audienceNetwork", "lifestreet", "pubmatic"}, parseSyncs(t, rr.Body.Bytes()))
+ assert.ElementsMatch(t, []string{"appnexus", "audienceNetwork", "pubmatic"}, parseSyncs(t, rr.Body.Bytes()))
assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes()))
}
func TestCookieSyncNoCookiesBrokenGDPR(t *testing.T) {
- rr := doConfigurablePost(`{"bidders":["appnexus", "audienceNetwork", "random"],"gdpr_consent":"GLKHGKGKKGK"}`, nil, true, map[openrtb_ext.BidderName]usersync.Usersyncer{}, config.GDPR{UsersyncIfAmbiguous: true}, config.CCPA{})
+ rr := doConfigurablePost(`{"bidders":["appnexus", "audienceNetwork", "random"],"gdpr_consent":"GLKHGKGKKGK"}`, nil, true, map[openrtb_ext.BidderName]usersync.Usersyncer{}, config.GDPR{DefaultValue: "0"}, config.CCPA{})
assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8")
assert.Equal(t, http.StatusOK, rr.Code)
assert.ElementsMatch(t, []string{"appnexus", "audienceNetwork"}, parseSyncs(t, rr.Body.Bytes()))
@@ -203,7 +202,6 @@ func syncersForTest() map[openrtb_ext.BidderName]usersync.Usersyncer {
return map[openrtb_ext.BidderName]usersync.Usersyncer{
openrtb_ext.BidderAppnexus: appnexus.NewAppnexusSyncer(template.Must(template.New("sync").Parse("someurl.com"))),
openrtb_ext.BidderAudienceNetwork: audienceNetwork.NewFacebookSyncer(template.Must(template.New("sync").Parse("https://www.facebook.com/audiencenetwork/idsync/?partner=partnerId&callback=localhost%2Fsetuid%3Fbidder%3DaudienceNetwork%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID"))),
- openrtb_ext.BidderLifestreet: lifestreet.NewLifestreetSyncer(template.Must(template.New("sync").Parse("anotherurl.com"))),
openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticSyncer(template.Must(template.New("sync").Parse("thaturl.com"))),
}
}
@@ -254,6 +252,6 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi
return ok, nil
}
-func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) {
+func (g *gdprPerms) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest, passGeo bool, passID bool, err error) {
return true, true, true, nil
}
diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go
index 6f290b22499..94a81b00d20 100644
--- a/endpoints/events/vtrack_test.go
+++ b/endpoints/events/vtrack_test.go
@@ -938,7 +938,7 @@ func TestGetVideoEventTracking(t *testing.T) {
name: "valid_scenario",
args: args{
trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]",
- bid: &openrtb2.Bid{
+ bid: &openrtb2.Bid{
// AdM: vastXMLWith2Creatives,
},
req: &openrtb2.BidRequest{
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 6767151f15f..6b4fa6890b8 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -182,13 +182,16 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
return
}
+ secGPC := r.Header.Get("Sec-GPC")
+
auctionRequest := exchange.AuctionRequest{
- BidRequest: req,
- Account: *account,
- UserSyncs: usersyncs,
- RequestType: labels.RType,
- StartTime: start,
- LegacyLabels: labels,
+ BidRequest: req,
+ Account: *account,
+ UserSyncs: usersyncs,
+ RequestType: labels.RType,
+ StartTime: start,
+ LegacyLabels: labels,
+ GlobalPrivacyControlHeader: secGPC,
}
response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
@@ -307,7 +310,7 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr
// At this point, we should have a valid request that definitely has Targeting and Cache turned on
- e = deps.validateRequest(req)
+ e = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: req})
errs = append(errs, e...)
return
}
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 079b9adb6d4..2ce1180d50b 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -32,7 +32,6 @@ func TestGoodAmpRequests(t *testing.T) {
goodRequests := map[string]json.RawMessage{
"1": json.RawMessage(validRequest(t, "aliased-buyeruids.json")),
"2": json.RawMessage(validRequest(t, "aliases.json")),
- "4": json.RawMessage(validRequest(t, "digitrust.json")),
"5": json.RawMessage(validRequest(t, "gdpr-no-consentstring.json")),
"6": json.RawMessage(validRequest(t, "gdpr.json")),
"7": json.RawMessage(validRequest(t, "site.json")),
@@ -67,6 +66,7 @@ func TestGoodAmpRequests(t *testing.T) {
var response AmpResponse
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
+ t.Errorf("AMP response was: %s", recorder.Body.Bytes())
t.Fatalf("Error unmarshalling response: %s", err.Error())
}
@@ -122,11 +122,6 @@ func TestAMPPageInfo(t *testing.T) {
func TestGDPRConsent(t *testing.T) {
consent := "BOu5On0Ou5On0ADACHENAO7pqzAAppY"
existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY"
- digitrust := &openrtb_ext.ExtUserDigiTrust{
- ID: "anyDigitrustID",
- KeyV: 1,
- Pref: 0,
- }
testCases := []struct {
description string
@@ -165,12 +160,10 @@ func TestGDPRConsent(t *testing.T) {
description: "Overrides Existing Consent - With Sibling Data",
consent: consent,
userExt: &openrtb_ext.ExtUser{
- Consent: existingConsent,
- DigiTrust: digitrust,
+ Consent: existingConsent,
},
expectedUserExt: openrtb_ext.ExtUser{
- Consent: consent,
- DigiTrust: digitrust,
+ Consent: consent,
},
},
{
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index bc8946fa90c..a6b3479a36c 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -37,6 +37,7 @@ import (
"github.com/prebid/prebid-server/util/httputil"
"github.com/prebid/prebid-server/util/iputil"
"golang.org/x/net/publicsuffix"
+ "golang.org/x/text/currency"
)
const storedRequestTimeoutMillis = 50
@@ -173,18 +174,28 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
return
}
+ // rebuild/resync the request in the request wrapper.
+ if err := req.RebuildRequest(); err != nil {
+ errL = append(errL, err)
+ writeError(errL, w, &labels)
+ return
+ }
+
+ secGPC := r.Header.Get("Sec-GPC")
+
auctionRequest := exchange.AuctionRequest{
- BidRequest: req,
- Account: *account,
- UserSyncs: usersyncs,
- RequestType: labels.RType,
- StartTime: start,
- LegacyLabels: labels,
- Warnings: warnings,
+ BidRequest: req.BidRequest,
+ Account: *account,
+ UserSyncs: usersyncs,
+ RequestType: labels.RType,
+ StartTime: start,
+ LegacyLabels: labels,
+ Warnings: warnings,
+ GlobalPrivacyControlHeader: secGPC,
}
response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
- ao.Request = req
+ ao.Request = req.BidRequest
ao.Response = response
ao.Account = account
if err != nil {
@@ -223,8 +234,9 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
// possible, it will return errors with messages that suggest improvements.
//
// If the errors list has at least one element, then no guarantees are made about the returned request.
-func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb2.BidRequest, errs []error) {
- req = &openrtb2.BidRequest{}
+func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, errs []error) {
+ req = &openrtb_ext.RequestWrapper{}
+ req.BidRequest = &openrtb2.BidRequest{}
errs = nil
// Pull the request body into a buffer, so we have it for later usage.
@@ -254,20 +266,20 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb2
return
}
- if err := json.Unmarshal(requestJson, req); err != nil {
+ if err := json.Unmarshal(requestJson, req.BidRequest); err != nil {
errs = []error{err}
return
}
// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
- deps.setFieldsImplicitly(httpRequest, req)
+ deps.setFieldsImplicitly(httpRequest, req.BidRequest)
if err := processInterstitials(req); err != nil {
errs = []error{err}
return
}
- lmt.ModifyForIOS(req)
+ lmt.ModifyForIOS(req.BidRequest)
errL := deps.validateRequest(req)
if len(errL) > 0 {
@@ -292,7 +304,7 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio
return defaultTimeout
}
-func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error {
+func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []error {
errL := []error{}
if req.ID == "" {
return []error{errors.New("request missing required field: \"id\"")}
@@ -314,30 +326,39 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error {
// If automatically filling source TID is enabled then validate that
// source.TID exists and If it doesn't, fill it with a randomly generated UUID
if deps.cfg.AutoGenSourceTID {
- if err := validateAndFillSourceTID(req); err != nil {
+ if err := validateAndFillSourceTID(req.BidRequest); err != nil {
return []error{err}
}
}
var aliases map[string]string
- if bidExt, err := deps.parseBidExt(req.Ext); err != nil {
+ reqExt, err := req.GetRequestExt()
+ if err != nil {
+ return []error{fmt.Errorf("request.ext is invalid: %v", err)}
+ }
+ reqPrebid := reqExt.GetPrebid()
+ if err := deps.parseBidExt(req); err != nil {
return []error{err}
- } else if bidExt != nil {
- aliases = bidExt.Prebid.Aliases
+ } else if reqPrebid != nil {
+ aliases = reqPrebid.Aliases
if err := deps.validateAliases(aliases); err != nil {
return []error{err}
}
- if err := deps.validateBidAdjustmentFactors(bidExt.Prebid.BidAdjustmentFactors, aliases); err != nil {
+ if err := deps.validateBidAdjustmentFactors(reqPrebid.BidAdjustmentFactors, aliases); err != nil {
+ return []error{err}
+ }
+
+ if err := validateSChains(reqPrebid.SChains); err != nil {
return []error{err}
}
- if err := validateSChains(bidExt); err != nil {
+ if err := deps.validateEidPermissions(reqPrebid.Data, aliases); err != nil {
return []error{err}
}
- if err := deps.validateEidPermissions(bidExt, aliases); err != nil {
+ if err := validateCustomRates(reqPrebid.CurrencyConversions); err != nil {
return []error{err}
}
}
@@ -346,19 +367,19 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error {
return append(errL, errors.New("request.site or request.app must be defined, but not both."))
}
- if err := deps.validateSite(req.Site); err != nil {
+ if err := deps.validateSite(req); err != nil {
return append(errL, err)
}
- if err := deps.validateApp(req.App); err != nil {
+ if err := deps.validateApp(req); err != nil {
return append(errL, err)
}
- if err := deps.validateUser(req.User, aliases); err != nil {
+ if err := deps.validateUser(req, aliases); err != nil {
return append(errL, err)
}
- if err := validateRegs(req.Regs); err != nil {
+ if err := validateRegs(req); err != nil {
return append(errL, err)
}
@@ -366,17 +387,18 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error {
return append(errL, err)
}
- if ccpaPolicy, err := ccpa.ReadFromRequest(req); err != nil {
+ if ccpaPolicy, err := ccpa.ReadFromRequestWrapper(req); err != nil {
return append(errL, err)
} else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil {
if _, invalidConsent := err.(*errortypes.Warning); invalidConsent {
errL = append(errL, &errortypes.Warning{
Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err),
WarningCode: errortypes.InvalidPrivacyConsentWarningCode})
- consentWriter := ccpa.ConsentWriter{Consent: ""}
- if err := consentWriter.Write(req); err != nil {
- return append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err))
+ regsExt, err := req.GetRegExt()
+ if err != nil {
+ return append(errL, err)
}
+ regsExt.SetUSPrivacy("")
} else {
return append(errL, err)
}
@@ -429,18 +451,42 @@ func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[str
return nil
}
-func validateSChains(req *openrtb_ext.ExtRequest) error {
- _, err := exchange.BidderToPrebidSChains(req)
+func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error {
+ _, err := exchange.BidderToPrebidSChains(sChains)
return err
}
-func (deps *endpointDeps) validateEidPermissions(req *openrtb_ext.ExtRequest, aliases map[string]string) error {
- if req == nil || req.Prebid.Data == nil {
+// validateCustomRates throws a bad input error if any of the 3-digit currency codes found in
+// the bidRequest.ext.prebid.currency field is invalid, malfomed or does not represent any actual
+// currency. No error is thrown if bidRequest.ext.prebid.currency is invalid or empty.
+func validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) error {
+ if bidReqCurrencyRates == nil {
return nil
}
- uniqueSources := make(map[string]struct{}, len(req.Prebid.Data.EidPermissions))
- for i, eid := range req.Prebid.Data.EidPermissions {
+ for fromCurrency, rates := range bidReqCurrencyRates.ConversionRates {
+ // Check if fromCurrency is a valid 3-letter currency code
+ if _, err := currency.ParseISO(fromCurrency); err != nil {
+ return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", fromCurrency)}
+ }
+
+ // Check if currencies mapped to fromCurrency are valid 3-letter currency codes
+ for toCurrency := range rates {
+ if _, err := currency.ParseISO(toCurrency); err != nil {
+ return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", toCurrency)}
+ }
+ }
+ }
+ return nil
+}
+
+func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, aliases map[string]string) error {
+ if prebid == nil {
+ return nil
+ }
+
+ uniqueSources := make(map[string]struct{}, len(prebid.EidPermissions))
+ for i, eid := range prebid.EidPermissions {
if len(eid.Source) == 0 {
return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] missing required field: "source"`, i)
}
@@ -657,7 +703,7 @@ func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.Cont
// Context is only recommended, so none is a valid type.
return nil
}
- if cType < native1.ContextTypeContent || cType > native1.ContextTypeProduct {
+ if cType < native1.ContextTypeContent || (cType > native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound) {
return fmt.Errorf("request.imp[%d].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex)
}
if cSubtype < 0 {
@@ -666,28 +712,27 @@ func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.Cont
if cSubtype == 0 {
return nil
}
-
- if cSubtype >= 500 {
- return fmt.Errorf("request.imp[%d].native.request.contextsubtype can't be greater than or equal to 500. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex)
- }
if cSubtype >= native1.ContextSubTypeGeneral && cSubtype <= native1.ContextSubTypeUserGenerated {
- if cType != native1.ContextTypeContent {
+ if cType != native1.ContextTypeContent && cType < openrtb_ext.NativeExchangeSpecificLowerBound {
return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype)
}
return nil
}
if cSubtype >= native1.ContextSubTypeSocial && cSubtype <= native1.ContextSubTypeChat {
- if cType != native1.ContextTypeSocial {
+ if cType != native1.ContextTypeSocial && cType < openrtb_ext.NativeExchangeSpecificLowerBound {
return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype)
}
return nil
}
if cSubtype >= native1.ContextSubTypeSelling && cSubtype <= native1.ContextSubTypeProductReview {
- if cType != native1.ContextTypeProduct {
+ if cType != native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound {
return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype)
}
return nil
}
+ if cSubtype >= openrtb_ext.NativeExchangeSpecificLowerBound {
+ return nil
+ }
return fmt.Errorf("request.imp[%d].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex)
}
@@ -697,7 +742,7 @@ func validateNativePlacementType(pt native1.PlacementType, impIndex int) error {
// Placement Type is only reccomended, not required.
return nil
}
- if pt < native1.PlacementTypeFeed || pt > native1.PlacementTypeRecommendationWidget {
+ if pt < native1.PlacementTypeFeed || (pt > native1.PlacementTypeRecommendationWidget && pt < openrtb_ext.NativeExchangeSpecificLowerBound) {
return fmt.Errorf("request.imp[%d].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex)
}
return nil
@@ -803,14 +848,14 @@ func validateNativeAssetTitle(title *nativeRequests.Title, impIndex int, assetIn
}
func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex int, eventIndex int) error {
- if tracker.Event < native1.EventTypeImpression || tracker.Event > native1.EventTypeViewableVideo50 {
+ if tracker.Event < native1.EventTypeImpression || (tracker.Event > native1.EventTypeViewableVideo50 && tracker.Event < openrtb_ext.NativeExchangeSpecificLowerBound) {
return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex)
}
if len(tracker.Methods) < 1 {
return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex)
}
for methodIndex, method := range tracker.Methods {
- if method < native1.EventTrackingMethodImage || method > native1.EventTrackingMethodJS {
+ if method < native1.EventTrackingMethodImage || (method > native1.EventTrackingMethodJS && method < openrtb_ext.NativeExchangeSpecificLowerBound) {
return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].methods[%d] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex, methodIndex)
}
}
@@ -852,7 +897,7 @@ func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIn
}
func validateNativeAssetData(data *nativeRequests.Data, impIndex int, assetIndex int) error {
- if data.Type < native1.DataAssetTypeSponsored || data.Type > native1.DataAssetTypeCTAText {
+ if data.Type < native1.DataAssetTypeSponsored || (data.Type > native1.DataAssetTypeCTAText && data.Type < 500) {
return fmt.Errorf("request.imp[%d].native.request.assets[%d].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex, assetIndex)
}
@@ -1014,15 +1059,11 @@ func isBidderToValidate(bidder string) bool {
}
}
-func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequest, error) {
- if len(ext) < 1 {
- return nil, nil
- }
- var tmpExt openrtb_ext.ExtRequest
- if err := json.Unmarshal(ext, &tmpExt); err != nil {
- return nil, fmt.Errorf("request.ext is invalid: %v", err)
+func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error {
+ if _, err := req.GetRequestExt(); err != nil {
+ return fmt.Errorf("request.ext is invalid: %v", err)
}
- return &tmpExt, nil
+ return nil
}
func (deps *endpointDeps) validateAliases(aliases map[string]string) error {
@@ -1042,124 +1083,112 @@ func (deps *endpointDeps) validateAliases(aliases map[string]string) error {
return nil
}
-func (deps *endpointDeps) validateSite(site *openrtb2.Site) error {
- if site == nil {
+func (deps *endpointDeps) validateSite(req *openrtb_ext.RequestWrapper) error {
+ if req.Site == nil {
return nil
}
- if site.ID == "" && site.Page == "" {
+ if req.Site.ID == "" && req.Site.Page == "" {
return errors.New("request.site should include at least one of request.site.id or request.site.page.")
}
- if len(site.Ext) > 0 {
- var s openrtb_ext.ExtSite
- if err := json.Unmarshal(site.Ext, &s); err != nil {
- return err
- }
+ siteExt, err := req.GetSiteExt()
+ if err != nil {
+ return err
+ }
+ siteAmp := siteExt.GetAmp()
+ if siteAmp < 0 || siteAmp > 1 {
+ return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`)
}
return nil
}
-func (deps *endpointDeps) validateApp(app *openrtb2.App) error {
- if app == nil {
+func (deps *endpointDeps) validateApp(req *openrtb_ext.RequestWrapper) error {
+ if req.App == nil {
return nil
}
- if app.ID != "" {
- if _, found := deps.cfg.BlacklistedAppMap[app.ID]; found {
- return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", app.ID)}
- }
- }
-
- if len(app.Ext) > 0 {
- var a openrtb_ext.ExtApp
- if err := json.Unmarshal(app.Ext, &a); err != nil {
- return err
+ if req.App.ID != "" {
+ if _, found := deps.cfg.BlacklistedAppMap[req.App.ID]; found {
+ return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)}
}
}
- return nil
+ _, err := req.GetAppExt()
+ return err
}
-func (deps *endpointDeps) validateUser(user *openrtb2.User, aliases map[string]string) error {
- if user == nil {
- return nil
- }
-
+func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases map[string]string) error {
// The following fields were previously uints in the OpenRTB library we use, but have
// since been changed to ints. We decided to maintain the non-negative check.
- if user.Geo != nil && user.Geo.Accuracy < 0 {
- return errors.New("request.user.geo.accuracy must be a positive number")
+ if req != nil && req.BidRequest != nil && req.User != nil {
+ if req.User.Geo != nil && req.User.Geo.Accuracy < 0 {
+ return errors.New("request.user.geo.accuracy must be a positive number")
+ }
}
- if user.Ext != nil {
- // Creating ExtUser object to check if DigiTrust is valid
- var userExt openrtb_ext.ExtUser
- if err := json.Unmarshal(user.Ext, &userExt); err == nil {
- if userExt.DigiTrust != nil && userExt.DigiTrust.Pref != 0 {
- // DigiTrust is not valid. Return error.
- return errors.New("request.user contains a digitrust object that is not valid.")
- }
- // Check if the buyeruids are valid
- if userExt.Prebid != nil {
- if len(userExt.Prebid.BuyerUIDs) < 1 {
- return errors.New(`request.user.ext.prebid requires a "buyeruids" property with at least one ID defined. If none exist, then request.user.ext.prebid should not be defined.`)
- }
- for bidderName := range userExt.Prebid.BuyerUIDs {
- if _, ok := deps.bidderMap[bidderName]; !ok {
- if _, ok := aliases[bidderName]; !ok {
- return fmt.Errorf("request.user.ext.%s is neither a known bidder name nor an alias in request.ext.prebid.aliases.", bidderName)
- }
- }
+ userExt, err := req.GetUserExt()
+ if err != nil {
+ return fmt.Errorf("request.user.ext object is not valid: %v", err)
+ }
+ // Check if the buyeruids are valid
+ prebid := userExt.GetPrebid()
+ if prebid != nil {
+ if len(prebid.BuyerUIDs) < 1 {
+ return errors.New(`request.user.ext.prebid requires a "buyeruids" property with at least one ID defined. If none exist, then request.user.ext.prebid should not be defined.`)
+ }
+ for bidderName := range prebid.BuyerUIDs {
+ if _, ok := deps.bidderMap[bidderName]; !ok {
+ if _, ok := aliases[bidderName]; !ok {
+ return fmt.Errorf("request.user.ext.%s is neither a known bidder name nor an alias in request.ext.prebid.aliases.", bidderName)
}
}
- // Check Universal User ID
- if userExt.Eids != nil {
- if len(userExt.Eids) == 0 {
- return fmt.Errorf("request.user.ext.eids must contain at least one element or be undefined")
- }
- uniqueSources := make(map[string]struct{}, len(userExt.Eids))
- for eidIndex, eid := range userExt.Eids {
- if eid.Source == "" {
- return fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex)
- }
- if _, ok := uniqueSources[eid.Source]; ok {
- return fmt.Errorf("request.user.ext.eids must contain unique sources")
- }
- uniqueSources[eid.Source] = struct{}{}
+ }
+ }
+ // Check Universal User ID
+ eids := userExt.GetEid()
+ if eids != nil {
+ if len(*eids) == 0 {
+ return errors.New("request.user.ext.eids must contain at least one element or be undefined")
+ }
+ uniqueSources := make(map[string]struct{}, len(*eids))
+ for eidIndex, eid := range *eids {
+ if eid.Source == "" {
+ return fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex)
+ }
+ if _, ok := uniqueSources[eid.Source]; ok {
+ return errors.New("request.user.ext.eids must contain unique sources")
+ }
+ uniqueSources[eid.Source] = struct{}{}
- if eid.ID == "" && eid.Uids == nil {
- return fmt.Errorf("request.user.ext.eids[%d] must contain either \"id\" or \"uids\" field", eidIndex)
- }
- if eid.ID == "" {
- if len(eid.Uids) == 0 {
- return fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", eidIndex)
- }
- for uidIndex, uid := range eid.Uids {
- if uid.ID == "" {
- return fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex)
- }
- }
+ if eid.ID == "" && eid.Uids == nil {
+ return fmt.Errorf("request.user.ext.eids[%d] must contain either \"id\" or \"uids\" field", eidIndex)
+ }
+ if eid.ID == "" {
+ if len(eid.Uids) == 0 {
+ return fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", eidIndex)
+ }
+ for uidIndex, uid := range eid.Uids {
+ if uid.ID == "" {
+ return fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex)
}
}
}
- } else {
- return fmt.Errorf("request.user.ext object is not valid: %v", err)
}
}
return nil
}
-func validateRegs(regs *openrtb2.Regs) error {
- if regs != nil && len(regs.Ext) > 0 {
- var regsExt openrtb_ext.ExtRegs
- if err := json.Unmarshal(regs.Ext, ®sExt); err != nil {
- return fmt.Errorf("request.regs.ext is invalid: %v", err)
- }
- if regsExt.GDPR != nil && (*regsExt.GDPR < 0 || *regsExt.GDPR > 1) {
- return errors.New("request.regs.ext.gdpr must be either 0 or 1.")
- }
+func validateRegs(req *openrtb_ext.RequestWrapper) error {
+ regsExt, err := req.GetRegExt()
+ if err != nil {
+ return fmt.Errorf("request.regs.ext is invalid: %v", err)
+ }
+ regExt := regsExt.GetExt()
+ gdprJSON, hasGDPR := regExt["gdpr"]
+ if hasGDPR && (string(gdprJSON) != "0" && string(gdprJSON) != "1") {
+ return errors.New("request.regs.ext.gdpr must be either 0 or 1.")
}
return nil
}
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 75d0610cb34..4835dd92943 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -17,17 +17,19 @@ import (
"testing"
"time"
- "github.com/mxmCherry/openrtb/v15/openrtb2"
- "github.com/prebid/prebid-server/stored_requests"
-
"github.com/buger/jsonparser"
jsonpatch "github.com/evanphx/json-patch"
+ "github.com/mxmCherry/openrtb/v15/native1"
+ nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request"
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
analyticsConf "github.com/prebid/prebid-server/analytics/config"
"github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/currency"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/exchange"
"github.com/prebid/prebid-server/metrics"
"github.com/prebid/prebid-server/openrtb_ext"
+ "github.com/prebid/prebid-server/stored_requests"
"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
"github.com/prebid/prebid-server/util/iputil"
"github.com/stretchr/testify/assert"
@@ -44,11 +46,13 @@ type testCase struct {
}
type testConfigValues struct {
- AccountRequired bool `json:"accountRequired"`
- AliasJSON string `json:"aliases"`
- BlacklistedAccounts []string `json:"blacklistedAccts"`
- BlacklistedApps []string `json:"blacklistedApps"`
- DisabledAdapters []string `json:"disabledAdapters"`
+ AccountRequired bool `json:"accountRequired"`
+ AliasJSON string `json:"aliases"`
+ BlacklistedAccounts []string `json:"blacklistedAccts"`
+ BlacklistedApps []string `json:"blacklistedApps"`
+ DisabledAdapters []string `json:"disabledAdapters"`
+ CurrencyRates map[string]map[string]float64 `json:"currencyRates"`
+ MockBidder mockBidExchangeBidder `json:"mockBidder"`
}
func TestJsonSampleRequests(t *testing.T) {
@@ -104,6 +108,22 @@ func TestJsonSampleRequests(t *testing.T) {
"Requests with first party data context info found in imp[i].ext.prebid.bidder,context",
"first-party-data",
},
+ {
+ "Assert we correctly use the server conversion rates when needed",
+ "currency-conversion/server-rates/valid",
+ },
+ {
+ "Assert we correctly throw an error when no conversion rate was found in the server conversions map",
+ "currency-conversion/server-rates/errors",
+ },
+ {
+ "Assert we correctly use request-defined custom currency rates when present in root.ext",
+ "currency-conversion/custom-rates/valid",
+ },
+ {
+ "Assert we correctly validate request-defined custom currency rates when present in root.ext",
+ "currency-conversion/custom-rates/errors",
+ },
}
for _, test := range testSuites {
testCaseFiles, err := getTestFiles(filepath.Join("sample-requests", test.sampleRequestsSubDir))
@@ -247,6 +267,7 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o
assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile)
assert.Equalf(t, expectedBidResponse.BidID, actualBidResponse.BidID, "BidResponse.BidID doesn't match expected. Test: %s\n", testFile)
assert.Equalf(t, expectedBidResponse.NBR, actualBidResponse.NBR, "BidResponse.NBR doesn't match expected. Test: %s\n", testFile)
+ assert.Equalf(t, expectedBidResponse.Cur, actualBidResponse.Cur, "BidResponse.Cur doesn't match expected. Test: %s\n", testFile)
//Assert []SeatBid and their Bid elements independently of their order
assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid array doesn't match expected. Test: %s\n", testFile)
@@ -440,8 +461,10 @@ func doRequest(t *testing.T, test testCase) (int, string) {
bidderMap := exchange.GetActiveBidders(bidderInfos)
disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos)
+ mockExchange := newMockBidExchange(test.Config.MockBidder, test.Config.CurrencyRates)
+
endpoint, _ := NewEndpoint(
- &mockBidExchange{},
+ mockExchange,
newParamsValidator(t),
&mockStoredReqFetcher{},
empty_fetcher.EmptyFetcher{},
@@ -1183,6 +1206,113 @@ func TestContentType(t *testing.T) {
}
}
+func TestValidateCustomRates(t *testing.T) {
+ boolTrue := true
+ boolFalse := false
+
+ testCases := []struct {
+ desc string
+ inBidReqCurrencies *openrtb_ext.ExtRequestCurrency
+ outCurrencyError error
+ }{
+ {
+ desc: "nil input, no errors expected",
+ inBidReqCurrencies: nil,
+ outCurrencyError: nil,
+ },
+ {
+ desc: "empty custom currency rates but UsePBSRates is set to false, we don't return error nor warning",
+ inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: map[string]map[string]float64{},
+ UsePBSRates: &boolFalse,
+ },
+ outCurrencyError: nil,
+ },
+ {
+ desc: "empty custom currency rates but UsePBSRates is set to true, no need to return error because we can use PBS rates",
+ inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: map[string]map[string]float64{},
+ UsePBSRates: &boolTrue,
+ },
+ outCurrencyError: nil,
+ },
+ {
+ desc: "UsePBSRates is nil and defaults to true, bidExt fromCurrency is invalid, expect bad input error",
+ inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: map[string]map[string]float64{
+ "FOO": {
+ "GBP": 1.2,
+ "MXN": 0.05,
+ "JPY": 0.95,
+ },
+ },
+ },
+ outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
+ },
+ {
+ desc: "UsePBSRates set to false, bidExt fromCurrency is invalid, expect bad input error",
+ inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: map[string]map[string]float64{
+ "FOO": {
+ "GBP": 1.2,
+ "MXN": 0.05,
+ "JPY": 0.95,
+ },
+ },
+ UsePBSRates: &boolFalse,
+ },
+ outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
+ },
+ {
+ desc: "UsePBSRates set to false, some of the bidExt 'to' Currencies are invalid, expect bad input error when parsing the first invalid currency code",
+ inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: map[string]map[string]float64{
+ "USD": {
+ "FOO": 10.0,
+ "MXN": 0.05,
+ },
+ },
+ UsePBSRates: &boolFalse,
+ },
+ outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
+ },
+ {
+ desc: "UsePBSRates set to false, some of the bidExt 'from' and 'to' currencies are invalid, expect bad input error when parsing the first invalid currency code",
+ inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: map[string]map[string]float64{
+ "FOO": {
+ "MXN": 0.05,
+ "CAD": 0.95,
+ },
+ },
+ UsePBSRates: &boolFalse,
+ },
+ outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
+ },
+ {
+ desc: "All 3-digit currency codes exist, expect no error",
+ inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: map[string]map[string]float64{
+ "USD": {
+ "MXN": 0.05,
+ },
+ "MXN": {
+ "JPY": 10.0,
+ "EUR": 10.95,
+ },
+ },
+ UsePBSRates: &boolFalse,
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+ actualErr := validateCustomRates(tc.inBidReqCurrencies)
+
+ assert.Equal(t, tc.outCurrencyError, actualErr, tc.desc)
+ }
+}
+
func TestValidateImpExt(t *testing.T) {
type testCase struct {
description string
@@ -1457,7 +1587,7 @@ func TestCurrencyTrunc(t *testing.T) {
Cur: []string{"USD", "EUR"},
}
- errL := deps.validateRequest(&req)
+ errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req})
expectedError := errortypes.Warning{Message: "A prebid request can only process one currency. Taking the first currency in the list, USD, as the active currency"}
assert.ElementsMatch(t, errL, []error{&expectedError})
@@ -1503,14 +1633,12 @@ func TestCCPAInvalid(t *testing.T) {
},
}
- errL := deps.validateRequest(&req)
+ errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req})
expectedWarning := errortypes.Warning{
Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)",
WarningCode: errortypes.InvalidPrivacyConsentWarningCode}
assert.ElementsMatch(t, errL, []error{&expectedWarning})
-
- assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request")
}
func TestNoSaleInvalid(t *testing.T) {
@@ -1554,7 +1682,7 @@ func TestNoSaleInvalid(t *testing.T) {
Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`),
}
- errL := deps.validateRequest(&req)
+ errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req})
expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided")
assert.ElementsMatch(t, errL, []error{expectedError})
@@ -1601,7 +1729,7 @@ func TestValidateSourceTID(t *testing.T) {
},
}
- deps.validateRequest(&req)
+ deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req})
assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID")
}
@@ -1643,7 +1771,7 @@ func TestSChainInvalid(t *testing.T) {
Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
}
- errL := deps.validateRequest(&req)
+ errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req})
expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.")
assert.ElementsMatch(t, errL, []error{expectedError})
@@ -1862,7 +1990,7 @@ func TestEidPermissionsInvalid(t *testing.T) {
Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`),
}
- errL := deps.validateRequest(&req)
+ errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req})
expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`)
assert.ElementsMatch(t, errL, []error{expectedError})
@@ -1877,11 +2005,6 @@ func TestValidateEidPermissions(t *testing.T) {
request *openrtb_ext.ExtRequest
expectedError error
}{
- {
- description: "Valid - Nil ext",
- request: nil,
- expectedError: nil,
- },
{
description: "Valid - Empty ext",
request: &openrtb_ext.ExtRequest{},
@@ -1966,7 +2089,7 @@ func TestValidateEidPermissions(t *testing.T) {
endpoint := &endpointDeps{bidderMap: knownBidders}
for _, test := range testCases {
- result := endpoint.validateEidPermissions(test.request, knownAliases)
+ result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases)
assert.Equal(t, test.expectedError, result, test.description)
}
}
@@ -2145,6 +2268,425 @@ func TestAuctionWarnings(t *testing.T) {
assert.Equal(t, errortypes.InvalidPrivacyConsentWarningCode, actualWarning.WarningCode, "Warning code is incorrect")
}
+func TestValidateNativeContextTypes(t *testing.T) {
+ impIndex := 4
+
+ testCases := []struct {
+ description string
+ givenContextType native1.ContextType
+ givenSubType native1.ContextSubType
+ expectedError string
+ }{
+ {
+ description: "No Types Specified",
+ givenContextType: 0,
+ givenSubType: 0,
+ expectedError: "",
+ },
+ {
+ description: "All Types Exchange Specific",
+ givenContextType: 500,
+ givenSubType: 500,
+ expectedError: "",
+ },
+ {
+ description: "Context Type Known Value - Sub Type Unspecified",
+ givenContextType: 1,
+ givenSubType: 0,
+ expectedError: "",
+ },
+ {
+ description: "Context Type Negative",
+ givenContextType: -1,
+ givenSubType: 0,
+ expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Context Type Just Above Range",
+ givenContextType: 4, // Range is currently 1-3
+ givenSubType: 0,
+ expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Sub Type Negative",
+ givenContextType: 1,
+ givenSubType: -1,
+ expectedError: "request.imp[4].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Content - Sub Type Just Below Range",
+ givenContextType: 1, // Content constant
+ givenSubType: 9, // Content range is currently 10-15
+ expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Content - Sub Type In Range",
+ givenContextType: 1, // Content constant
+ givenSubType: 10, // Content range is currently 10-15
+ expectedError: "",
+ },
+ {
+ description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary",
+ givenContextType: 500,
+ givenSubType: 10, // Content range is currently 10-15
+ expectedError: "",
+ },
+ {
+ description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary + 1",
+ givenContextType: 501,
+ givenSubType: 10, // Content range is currently 10-15
+ expectedError: "",
+ },
+ {
+ description: "Content - Sub Type Just Above Range",
+ givenContextType: 1, // Content constant
+ givenSubType: 16, // Content range is currently 10-15
+ expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Content - Sub Type Exchange Specific Boundary",
+ givenContextType: 1, // Content constant
+ givenSubType: 500,
+ expectedError: "",
+ },
+ {
+ description: "Content - Sub Type Exchange Specific Boundary + 1",
+ givenContextType: 1, // Content constant
+ givenSubType: 501,
+ expectedError: "",
+ },
+ {
+ description: "Content - Invalid Context Type",
+ givenContextType: 2, // Not content constant
+ givenSubType: 10, // Content range is currently 10-15
+ expectedError: "request.imp[4].native.request.context is 2, but contextsubtype is 10. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Social - Sub Type Just Below Range",
+ givenContextType: 2, // Social constant
+ givenSubType: 19, // Social range is currently 20-22
+ expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Social - Sub Type In Range",
+ givenContextType: 2, // Social constant
+ givenSubType: 20, // Social range is currently 20-22
+ expectedError: "",
+ },
+ {
+ description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary",
+ givenContextType: 500,
+ givenSubType: 20, // Social range is currently 20-22
+ expectedError: "",
+ },
+ {
+ description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary + 1",
+ givenContextType: 501,
+ givenSubType: 20, // Social range is currently 20-22
+ expectedError: "",
+ },
+ {
+ description: "Social - Sub Type Just Above Range",
+ givenContextType: 2, // Social constant
+ givenSubType: 23, // Social range is currently 20-22
+ expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Social - Sub Type Exchange Specific Boundary",
+ givenContextType: 2, // Social constant
+ givenSubType: 500,
+ expectedError: "",
+ },
+ {
+ description: "Social - Sub Type Exchange Specific Boundary + 1",
+ givenContextType: 2, // Social constant
+ givenSubType: 501,
+ expectedError: "",
+ },
+ {
+ description: "Social - Invalid Context Type",
+ givenContextType: 3, // Not social constant
+ givenSubType: 20, // Social range is currently 20-22
+ expectedError: "request.imp[4].native.request.context is 3, but contextsubtype is 20. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Product - Sub Type Just Below Range",
+ givenContextType: 3, // Product constant
+ givenSubType: 29, // Product range is currently 30-32
+ expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Product - Sub Type In Range",
+ givenContextType: 3, // Product constant
+ givenSubType: 30, // Product range is currently 30-32
+ expectedError: "",
+ },
+ {
+ description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary",
+ givenContextType: 500,
+ givenSubType: 30, // Product range is currently 30-32
+ expectedError: "",
+ },
+ {
+ description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary + 1",
+ givenContextType: 501,
+ givenSubType: 30, // Product range is currently 30-32
+ expectedError: "",
+ },
+ {
+ description: "Product - Sub Type Just Above Range",
+ givenContextType: 3, // Product constant
+ givenSubType: 33, // Product range is currently 30-32
+ expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ {
+ description: "Product - Sub Type Exchange Specific Boundary",
+ givenContextType: 3, // Product constant
+ givenSubType: 500,
+ expectedError: "",
+ },
+ {
+ description: "Product - Sub Type Exchange Specific Boundary + 1",
+ givenContextType: 3, // Product constant
+ givenSubType: 501,
+ expectedError: "",
+ },
+ {
+ description: "Product - Invalid Context Type",
+ givenContextType: 1, // Not product constant
+ givenSubType: 30, // Product range is currently 30-32
+ expectedError: "request.imp[4].native.request.context is 1, but contextsubtype is 30. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+ },
+ }
+
+ for _, test := range testCases {
+ err := validateNativeContextTypes(test.givenContextType, test.givenSubType, impIndex)
+ if test.expectedError == "" {
+ assert.NoError(t, err, test.description)
+ } else {
+ assert.EqualError(t, err, test.expectedError, test.description)
+ }
+ }
+}
+
+func TestValidateNativePlacementType(t *testing.T) {
+ impIndex := 4
+
+ testCases := []struct {
+ description string
+ givenPlacementType native1.PlacementType
+ expectedError string
+ }{
+ {
+ description: "Not Specified",
+ givenPlacementType: 0,
+ expectedError: "",
+ },
+ {
+ description: "Known Value",
+ givenPlacementType: 1, // Range is currently 1-4
+ expectedError: "",
+ },
+ {
+ description: "Exchange Specific - Boundary",
+ givenPlacementType: 500,
+ expectedError: "",
+ },
+ {
+ description: "Exchange Specific - Boundary + 1",
+ givenPlacementType: 501,
+ expectedError: "",
+ },
+ {
+ description: "Negative",
+ givenPlacementType: -1,
+ expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
+ },
+ {
+ description: "Just Above Range",
+ givenPlacementType: 5, // Range is currently 1-4
+ expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
+ },
+ }
+
+ for _, test := range testCases {
+ err := validateNativePlacementType(test.givenPlacementType, impIndex)
+ if test.expectedError == "" {
+ assert.NoError(t, err, test.description)
+ } else {
+ assert.EqualError(t, err, test.expectedError, test.description)
+ }
+ }
+}
+
+func TestValidateNativeEventTracker(t *testing.T) {
+ impIndex := 4
+ eventIndex := 8
+
+ testCases := []struct {
+ description string
+ givenEvent nativeRequests.EventTracker
+ expectedError string
+ }{
+ {
+ description: "Valid",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 1,
+ Methods: []native1.EventTrackingMethod{1},
+ },
+ expectedError: "",
+ },
+ {
+ description: "Event - Exchange Specific - Boundary",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 500,
+ Methods: []native1.EventTrackingMethod{1},
+ },
+ expectedError: "",
+ },
+ {
+ description: "Event - Exchange Specific - Boundary + 1",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 501,
+ Methods: []native1.EventTrackingMethod{1},
+ },
+ expectedError: "",
+ },
+ {
+ description: "Event - Negative",
+ givenEvent: nativeRequests.EventTracker{
+ Event: -1,
+ Methods: []native1.EventTrackingMethod{1},
+ },
+ expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
+ },
+ {
+ description: "Event - Just Above Range",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 5, // Range is currently 1-4
+ Methods: []native1.EventTrackingMethod{1},
+ },
+ expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
+ },
+ {
+ description: "Methods - Many Valid",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 1,
+ Methods: []native1.EventTrackingMethod{1, 2},
+ },
+ expectedError: "",
+ },
+ {
+ description: "Methods - Empty",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 1,
+ Methods: []native1.EventTrackingMethod{},
+ },
+ expectedError: "request.imp[4].native.request.eventtrackers[8].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
+ },
+ {
+ description: "Methods - Exchange Specific - Boundary",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 1,
+ Methods: []native1.EventTrackingMethod{500},
+ },
+ expectedError: "",
+ },
+ {
+ description: "Methods - Exchange Specific - Boundary + 1",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 1,
+ Methods: []native1.EventTrackingMethod{501},
+ },
+ expectedError: "",
+ },
+ {
+ description: "Methods - Negative",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 1,
+ Methods: []native1.EventTrackingMethod{-1},
+ },
+ expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
+ },
+ {
+ description: "Methods - Just Above Range",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 1,
+ Methods: []native1.EventTrackingMethod{3}, // Known values are currently 1-2
+ },
+ expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
+ },
+ {
+ description: "Methods - Mixed Valid + Invalid",
+ givenEvent: nativeRequests.EventTracker{
+ Event: 1,
+ Methods: []native1.EventTrackingMethod{1, -1},
+ },
+ expectedError: "request.imp[4].native.request.eventtrackers[8].methods[1] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
+ },
+ }
+
+ for _, test := range testCases {
+ err := validateNativeEventTracker(test.givenEvent, impIndex, eventIndex)
+ if test.expectedError == "" {
+ assert.NoError(t, err, test.description)
+ } else {
+ assert.EqualError(t, err, test.expectedError, test.description)
+ }
+ }
+}
+
+func TestValidateNativeAssetData(t *testing.T) {
+ impIndex := 4
+ assetIndex := 8
+
+ testCases := []struct {
+ description string
+ givenData nativeRequests.Data
+ expectedError string
+ }{
+ {
+ description: "Valid",
+ givenData: nativeRequests.Data{Type: 1},
+ expectedError: "",
+ },
+ {
+ description: "Exchange Specific - Boundary",
+ givenData: nativeRequests.Data{Type: 500},
+ expectedError: "",
+ },
+ {
+ description: "Exchange Specific - Boundary + 1",
+ givenData: nativeRequests.Data{Type: 501},
+ expectedError: "",
+ },
+ {
+ description: "Not Specified",
+ givenData: nativeRequests.Data{},
+ expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
+ },
+ {
+ description: "Negative",
+ givenData: nativeRequests.Data{Type: -1},
+ expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
+ },
+ {
+ description: "Just Above Range",
+ givenData: nativeRequests.Data{Type: 13}, // Range is currently 1-12
+ expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
+ },
+ }
+
+ for _, test := range testCases {
+ err := validateNativeAssetData(&test.givenData, impIndex, assetIndex)
+ if test.expectedError == "" {
+ assert.NoError(t, err, test.description)
+ } else {
+ assert.EqualError(t, err, test.expectedError, test.description)
+ }
+ }
+}
+
// warningsCheckExchange is a well-behaved exchange which stores all incoming warnings.
type warningsCheckExchange struct {
auctionRequest exchange.AuctionRequest
@@ -2170,7 +2712,49 @@ func (e *nobidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReque
}
type mockBidExchange struct {
- gotRequest *openrtb2.BidRequest
+ mockBidder mockBidExchangeBidder
+ pbsRates map[string]map[string]float64
+}
+
+func newMockBidExchange(bidder mockBidExchangeBidder, mockCurrencyConversionRates map[string]map[string]float64) *mockBidExchange {
+ if bidder.BidCurrency == "" {
+ bidder.BidCurrency = "USD"
+ }
+
+ return &mockBidExchange{
+ mockBidder: bidder,
+ pbsRates: mockCurrencyConversionRates,
+ }
+}
+
+// getAuctionCurrencyRates copies the logic of the exchange package for testing purposes
+func (e *mockBidExchange) getAuctionCurrencyRates(customRates *openrtb_ext.ExtRequestCurrency) currency.Conversions {
+ if customRates == nil {
+ // The timestamp is required for the function signature, but is not used and its
+ // value has no significance in the tests
+ return currency.NewRates(time.Now(), e.pbsRates)
+ }
+
+ usePbsRates := true
+ if customRates.UsePBSRates != nil {
+ usePbsRates = *customRates.UsePBSRates
+ }
+
+ if !usePbsRates {
+ // The timestamp is required for the function signature, but is not used and its
+ // value has no significance in the tests
+ return currency.NewRates(time.Now(), customRates.ConversionRates)
+ }
+
+ // Both PBS and custom rates can be used, check if ConversionRates is not empty
+ if len(customRates.ConversionRates) == 0 {
+ // Custom rates map is empty, use PBS rates only
+ return currency.NewRates(time.Now(), e.pbsRates)
+ }
+
+ // Return an AggregateConversions object that includes both custom and PBS currency rates but will
+ // prioritize custom rates over PBS rates whenever a currency rate is found in both
+ return currency.NewAggregateConversions(currency.NewRates(time.Time{}, customRates.ConversionRates), currency.NewRates(time.Now(), e.pbsRates))
}
// mockBidExchange is a well-behaved exchange that lists the bidders found in every bidRequest.Imp[i].Ext
@@ -2181,6 +2765,36 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq
BidID: "test bid id",
NBR: openrtb2.NoBidReasonCodeUnknownError.Ptr(),
}
+
+ // Use currencies inside r.BidRequest.Cur, if any, and convert currencies if needed
+ if len(r.BidRequest.Cur) == 0 {
+ r.BidRequest.Cur = []string{"USD"}
+ }
+
+ var currencyFrom string = e.mockBidder.getBidCurrency()
+ var conversionRate float64 = 0.00
+ var err error
+
+ var requestExt openrtb_ext.ExtRequest
+ if len(r.BidRequest.Ext) > 0 {
+ if err := json.Unmarshal(r.BidRequest.Ext, &requestExt); err != nil {
+ return nil, fmt.Errorf("request.ext is invalid: %v", err)
+ }
+ }
+
+ conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions)
+ for _, bidReqCur := range r.BidRequest.Cur {
+ if conversionRate, err = conversions.GetRate(currencyFrom, bidReqCur); err == nil {
+ bidResponse.Cur = bidReqCur
+ break
+ }
+ }
+
+ if conversionRate == 0 {
+ // Can't have bids if there's not even a 1 USD to 1 USD conversion rate
+ return nil, errors.New("Can't produce bid with no valid currency to use or currency conversion to convert to.")
+ }
+
if len(r.BidRequest.Imp) > 0 {
var SeatBidMap = make(map[string]openrtb2.SeatBid, 0)
for _, imp := range r.BidRequest.Imp {
@@ -2205,9 +2819,17 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq
for bidderNameOrAlias := range bidderExts {
if isBidderToValidate(bidderNameOrAlias) {
if val, ok := SeatBidMap[bidderNameOrAlias]; ok {
- val.Bid = append(val.Bid, openrtb2.Bid{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)})
+ val.Bid = append(val.Bid, openrtb2.Bid{ID: e.mockBidder.getBidId(bidderNameOrAlias)})
} else {
- SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{Seat: fmt.Sprintf("%s-bids", bidderNameOrAlias), Bid: []openrtb2.Bid{{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}}}
+ SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{
+ Seat: e.mockBidder.getSeatName(bidderNameOrAlias),
+ Bid: []openrtb2.Bid{
+ {
+ ID: e.mockBidder.getBidId(bidderNameOrAlias),
+ Price: e.mockBidder.getBidPrice() * conversionRate,
+ },
+ },
+ }
}
}
}
@@ -2220,6 +2842,24 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq
return bidResponse, nil
}
+type mockBidExchangeBidder struct {
+ BidCurrency string `json:"currency"`
+ BidPrice float64 `json:"price"`
+}
+
+func (bidder mockBidExchangeBidder) getBidCurrency() string {
+ return bidder.BidCurrency
+}
+func (bidder mockBidExchangeBidder) getBidPrice() float64 {
+ return bidder.BidPrice
+}
+func (bidder mockBidExchangeBidder) getSeatName(bidderNameOrAlias string) string {
+ return fmt.Sprintf("%s-bids", bidderNameOrAlias)
+}
+func (bidder mockBidExchangeBidder) getBidId(bidderNameOrAlias string) string {
+ return fmt.Sprintf("%s-bid", bidderNameOrAlias)
+}
+
type brokenExchange struct{}
func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
diff --git a/endpoints/openrtb2/ctv_auction.go b/endpoints/openrtb2/ctv_auction.go
index a2299517695..00a2b254b32 100644
--- a/endpoints/openrtb2/ctv_auction.go
+++ b/endpoints/openrtb2/ctv_auction.go
@@ -104,6 +104,7 @@ func NewCTVEndpoint(
func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
defer util.TimeTrack(time.Now(), "CTVAuctionEndpoint")
+ var reqWrapper *openrtb_ext.RequestWrapper
var request *openrtb2.BidRequest
var response *openrtb2.BidResponse
var err error
@@ -136,10 +137,11 @@ func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.R
}()
//Parse ORTB Request and do Standard Validation
- request, errL = deps.parseRequest(r)
+ reqWrapper, errL = deps.parseRequest(r)
if errortypes.ContainsFatalError(errL) && writeError(errL, w, &deps.labels) {
return
}
+ request = reqWrapper.BidRequest
util.JLogf("Original BidRequest", request) //TODO: REMOVE LOG
diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go
index 1aa2a7fc890..359bae11d4c 100644
--- a/endpoints/openrtb2/interstitial.go
+++ b/endpoints/openrtb2/interstitial.go
@@ -1,7 +1,6 @@
package openrtb2
import (
- "encoding/json"
"fmt"
"github.com/mxmCherry/openrtb/v15/openrtb2"
@@ -10,26 +9,27 @@ import (
"github.com/prebid/prebid-server/openrtb_ext"
)
-func processInterstitials(req *openrtb2.BidRequest) error {
- var devExt openrtb_ext.ExtDevice
+func processInterstitials(req *openrtb_ext.RequestWrapper) error {
unmarshalled := true
for i := range req.Imp {
if req.Imp[i].Instl == 1 {
+ var prebid *openrtb_ext.ExtDevicePrebid
if unmarshalled {
if req.Device.Ext == nil {
// No special interstitial support requested, so bail as there is nothing to do
return nil
}
- err := json.Unmarshal(req.Device.Ext, &devExt)
+ deviceExt, err := req.GetDeviceExt()
if err != nil {
return err
}
- if devExt.Prebid.Interstitial == nil {
+ prebid = deviceExt.GetPrebid()
+ if prebid.Interstitial == nil {
// No special interstitial support requested, so bail as there is nothing to do
return nil
}
}
- err := processInterstitialsForImp(&req.Imp[i], &devExt, req.Device)
+ err := processInterstitialsForImp(&req.Imp[i], prebid, req.Device)
if err != nil {
return err
}
@@ -38,7 +38,7 @@ func processInterstitials(req *openrtb2.BidRequest) error {
return nil
}
-func processInterstitialsForImp(imp *openrtb2.Imp, devExt *openrtb_ext.ExtDevice, device *openrtb2.Device) error {
+func processInterstitialsForImp(imp *openrtb2.Imp, devExtPrebid *openrtb_ext.ExtDevicePrebid, device *openrtb2.Device) error {
var maxWidth, maxHeight, minWidth, minHeight int64
if imp.Banner == nil {
// custom interstitial support is only available for banner requests.
@@ -56,8 +56,8 @@ func processInterstitialsForImp(imp *openrtb2.Imp, devExt *openrtb_ext.ExtDevice
maxWidth = device.W
maxHeight = device.H
}
- minWidth = (maxWidth * int64(devExt.Prebid.Interstitial.MinWidthPerc)) / 100
- minHeight = (maxHeight * int64(devExt.Prebid.Interstitial.MinHeightPerc)) / 100
+ minWidth = (maxWidth * devExtPrebid.Interstitial.MinWidthPerc) / 100
+ minHeight = (maxHeight * devExtPrebid.Interstitial.MinHeightPerc) / 100
imp.Banner.Format = genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight)
if len(imp.Banner.Format) == 0 {
return &errortypes.BadInput{Message: fmt.Sprintf("Unable to set interstitial size list for Imp id=%s (No valid sizes between %dx%d and %dx%d)", imp.ID, minWidth, minHeight, maxWidth, maxHeight)}
diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go
index 1d7ad9e3d6b..fe0ed966c3c 100644
--- a/endpoints/openrtb2/interstitial_test.go
+++ b/endpoints/openrtb2/interstitial_test.go
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
)
@@ -34,7 +35,7 @@ var request = &openrtb2.BidRequest{
func TestInterstitial(t *testing.T) {
myRequest := request
- if err := processInterstitials(myRequest); err != nil {
+ if err := processInterstitials(&openrtb_ext.RequestWrapper{BidRequest: myRequest}); err != nil {
t.Fatalf("Error processing interstitials: %v", err)
}
targetFormat := []openrtb2.Format{
diff --git a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json
index c3ab09d4883..75c859d212b 100644
--- a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json
+++ b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json
@@ -66,6 +66,7 @@
"expectedBidResponse": {
"id":"some-request-id",
"bidid":"test bid id",
+ "cur": "USD",
"nbr":0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json
index a72d184c81c..ae930384499 100644
--- a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json
+++ b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json
@@ -68,6 +68,7 @@
"expectedBidResponse": {
"id":"some-request-id",
"bidid":"test bid id",
+ "cur": "USD",
"nbr":0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json
index 55e45041e6e..00906c89772 100644
--- a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json
+++ b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json
@@ -87,6 +87,7 @@
}
],
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0
},
"expectedReturnCode": 200
diff --git a/endpoints/openrtb2/sample-requests/aliased/simple.json b/endpoints/openrtb2/sample-requests/aliased/simple.json
index a99907ab370..677d3d8cf53 100644
--- a/endpoints/openrtb2/sample-requests/aliased/simple.json
+++ b/endpoints/openrtb2/sample-requests/aliased/simple.json
@@ -27,19 +27,20 @@
"expectedBidResponse": {
"id":"some-request-id",
"bidid":"test bid id",
+ "cur": "USD",
"nbr":0,
- "seatbid": [
- {
- "bid": [
- {
- "id": "alias1-bid",
- "impid": "",
- "price": 0
- }
- ],
- "seat": "alias1-bids"
- }
- ]
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "alias1-bid",
+ "impid": "",
+ "price": 0
+ }
+ ],
+ "seat": "alias1-bids"
+ }
+ ]
},
"expectedReturnCode": 200
}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json
new file mode 100644
index 00000000000..03877031294
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json
@@ -0,0 +1,46 @@
+{
+ "description": "request.ext.prebid.currency.rates empty, usepbsrates is false, a conversion is needed but conversions are disabled",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 2.00
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["MXN"],
+ "ext": {
+ "prebid": {
+ "currency": {
+ "rates": {},
+ "usepbsrates": false
+ }
+ }
+ }
+ },
+ "expectedReturnCode": 500,
+ "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json
new file mode 100644
index 00000000000..6a727e9615c
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json
@@ -0,0 +1,52 @@
+{
+ "description": "currency in request.cur cannot be converted because conversion rate not found in either custom currency rates nor server rates. usepbsrates defaults to true",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 2.00
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["GBP"],
+ "ext": {
+ "prebid": {
+ "aliases": {
+ "unknown": "appnexus"
+ },
+ "currency": {
+ "rates": {
+ "USD": {
+ "MXN": 5.00
+ }
+ }
+ }
+ }
+ }
+ },
+ "expectedReturnCode": 500,
+ "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json
new file mode 100644
index 00000000000..5549fa9b688
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json
@@ -0,0 +1,53 @@
+{
+ "description": "currency in request.cur cannot be converted because usepbsrates set to false not allowing for PBS to use its rates. Default to price of 0",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 5.09
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["MXN"],
+ "ext": {
+ "prebid": {
+ "aliases": {
+ "unknown": "appnexus"
+ },
+ "currency": {
+ "rates": {
+ "USD": {
+ "JPY": 2.00
+ }
+ },
+ "usepbsrates": false
+ }
+ }
+ }
+ },
+ "expectedReturnCode": 500,
+ "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json
new file mode 100644
index 00000000000..f4e19f3a4c5
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json
@@ -0,0 +1,49 @@
+{
+ "description": "usepbsrates set to false forces BidRequest to use custom currency rates but bidRequest.ext.prebid.currency.rates field is empty",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 5.09
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["MXN"],
+ "ext": {
+ "prebid": {
+ "aliases": {
+ "unknown": "appnexus"
+ },
+ "currency": {
+ "rates": {},
+ "usepbsrates": false
+ }
+ }
+ }
+ },
+ "expectedReturnCode": 500,
+ "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json
new file mode 100644
index 00000000000..39857650f12
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json
@@ -0,0 +1,53 @@
+{
+ "description": "False usepbsrates forces BidRequest use custom currency rates but bidRequest.ext.prebid.currency.rates field comes with invalid currency codes",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 5.09
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["MXN"],
+ "ext": {
+ "prebid": {
+ "aliases": {
+ "unknown": "appnexus"
+ },
+ "currency": {
+ "rates": {
+ "USD": {
+ "FOO": 10.0
+ }
+ },
+ "usepbsrates": false
+ }
+ }
+ }
+ },
+ "expectedReturnCode": 400,
+ "expectedErrorMessage": "Invalid request: currency code "
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json
new file mode 100644
index 00000000000..0741ea4d315
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json
@@ -0,0 +1,62 @@
+{
+ "description": "request.ext.prebid.currency.rates empty, usepbsrates set to false, request succeeded because no conversion was needed",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 2.00
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "ext": {
+ "prebid": {
+ "currency": {
+ "rates": {},
+ "usepbsrates": false
+ }
+ }
+ }
+ },
+ "expectedBidResponse": {
+ "id":"some-request-id",
+ "bidid":"test bid id",
+ "cur": "USD",
+ "nbr":0,
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 1.00
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
+ },
+ "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json
new file mode 100644
index 00000000000..fb65a852355
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json
@@ -0,0 +1,70 @@
+{
+ "description": "request comes with custom rates but request.cur currency is only found in the server rates. Error wasn't thrown because usepbsrates defaults to true",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 2.00
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["MXN"],
+ "ext": {
+ "prebid": {
+ "aliases": {
+ "unknown": "appnexus"
+ },
+ "currency": {
+ "rates": {
+ "USD": {
+ "JPY": 15.00,
+ "EUR": 0.85
+ }
+ }
+ }
+ }
+ }
+ },
+ "expectedBidResponse": {
+ "id":"some-request-id",
+ "bidid":"test bid id",
+ "cur": "MXN",
+ "nbr":0,
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 2.00
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
+ },
+ "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json
new file mode 100644
index 00000000000..80790a52543
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json
@@ -0,0 +1,69 @@
+{
+ "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates defaults to true",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 2.00
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["MXN"],
+ "ext": {
+ "prebid": {
+ "aliases": {
+ "unknown": "appnexus"
+ },
+ "currency": {
+ "rates": {
+ "USD": {
+ "MXN": 5.00
+ }
+ }
+ }
+ }
+ }
+ },
+ "expectedBidResponse": {
+ "id":"some-request-id",
+ "bidid":"test bid id",
+ "cur": "MXN",
+ "nbr":0,
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 5.00
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
+ },
+ "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json
new file mode 100644
index 00000000000..ef372c1cf66
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json
@@ -0,0 +1,70 @@
+{
+ "description": "request.ext.prebid.currency substitutes those of the currency conversion server because usepbsrates is false",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 2.00
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["MXN"],
+ "ext": {
+ "prebid": {
+ "aliases": {
+ "unknown": "appnexus"
+ },
+ "currency": {
+ "rates": {
+ "USD": {
+ "MXN": 5.00
+ }
+ },
+ "usepbsrates": false
+ }
+ }
+ }
+ },
+ "expectedBidResponse": {
+ "id":"some-request-id",
+ "bidid":"test bid id",
+ "cur": "MXN",
+ "nbr":0,
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 5.00
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
+ },
+ "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json
new file mode 100644
index 00000000000..276e8da43c2
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json
@@ -0,0 +1,70 @@
+{
+ "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates was true",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 2.00
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["MXN"],
+ "ext": {
+ "prebid": {
+ "aliases": {
+ "unknown": "appnexus"
+ },
+ "currency": {
+ "rates": {
+ "USD": {
+ "MXN": 5.00
+ }
+ },
+ "usepbsrates": true
+ }
+ }
+ }
+ },
+ "expectedBidResponse": {
+ "id":"some-request-id",
+ "bidid":"test bid id",
+ "cur": "MXN",
+ "nbr":0,
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 5.00
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
+ },
+ "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json
new file mode 100644
index 00000000000..624f0784dac
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json
@@ -0,0 +1,66 @@
+{
+ "description": "USD BidRequest gets converted because mockbidder bids in foreign currency, custom conversion rate is used",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 8.00
+ }
+ },
+ "mockBidder": {
+ "currency": "MXN",
+ "price": 20.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "ext": {
+ "prebid": {
+ "currency": {
+ "rates": {
+ "USD": {
+ "MXN": 10.00
+ }
+ },
+ "usepbsrates": true
+ }
+ }
+ }
+ },
+ "expectedBidResponse": {
+ "id":"some-request-id",
+ "bidid":"test bid id",
+ "nbr":0,
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 2
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
+ },
+ "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json
new file mode 100644
index 00000000000..929c2e0cbd5
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json
@@ -0,0 +1,70 @@
+{
+ "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates was true",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 2.00
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ],
+ "cur": ["MXN"],
+ "ext": {
+ "prebid": {
+ "aliases": {
+ "unknown": "appnexus"
+ },
+ "currency": {
+ "rates": {
+ "USD": {
+ "CAD": 5.00
+ }
+ },
+ "usepbsrates": true
+ }
+ }
+ }
+ },
+ "expectedBidResponse": {
+ "id":"some-request-id",
+ "bidid":"test bid id",
+ "cur": "MXN",
+ "nbr":0,
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 2.00
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
+ },
+ "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json
new file mode 100644
index 00000000000..dc0d7ce6042
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json
@@ -0,0 +1,38 @@
+{
+ "description": "bid request calls for a bid in foreign currency MXN but conversion rate is not found in the currency conversion service.",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "GBP": 0.80
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "cur": ["MXN"],
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ]
+ },
+ "expectedReturnCode": 500,
+ "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+}
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json
new file mode 100644
index 00000000000..84788d5ada1
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json
@@ -0,0 +1,55 @@
+{
+ "description": "bid request calls for a bid in foreign currency but mockbidder bids in USD. Conversion rate is applied",
+ "config": {
+ "currencyRates":{
+ "USD": {
+ "MXN": 2.00
+ }
+ },
+ "mockBidder": {
+ "currency": "USD",
+ "price": 1.00
+ }
+ },
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "cur": ["MXN"],
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }
+ ]
+ },
+ "expectedBidResponse": {
+ "id":"some-request-id",
+ "bidid":"test bid id",
+ "cur": "MXN",
+ "nbr":0,
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 2.00
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
+ },
+ "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/disabled/good/partial.json b/endpoints/openrtb2/sample-requests/disabled/good/partial.json
index 3549abaa934..735e7c5ede1 100644
--- a/endpoints/openrtb2/sample-requests/disabled/good/partial.json
+++ b/endpoints/openrtb2/sample-requests/disabled/good/partial.json
@@ -58,6 +58,7 @@
"expectedBidResponse": {
"id":"some-request-id",
"bidid":"test bid id",
+ "cur": "USD",
"nbr":0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json
index c36ae0cd41d..a4b716b2040 100644
--- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json
+++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json
@@ -43,7 +43,8 @@
"seat": "appnexus-bids"
}],
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0
},
"expectedReturnCode": 200
-}
\ No newline at end of file
+}
diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json
index ad6298db39a..27e8c46d9d7 100644
--- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json
+++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json
@@ -47,7 +47,8 @@
"seat": "appnexus-bids"
}],
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0
},
"expectedReturnCode": 200
-}
\ No newline at end of file
+}
diff --git a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json
deleted file mode 100644
index 9b422380edf..00000000000
--- a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "description": "Native bid request comes with a subcontext type greater than 500. Return error",
- "mockBidRequest": {
- "id": "req-id",
- "site": {
- "page": "some.page.com"
- },
- "tmax": 500,
- "imp": [
- {
- "id": "some-imp",
- "native": {
- "request": "{\"context\":1,\"contextsubtype\":550,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}"
- },
- "ext": {
- "appnexus": {
- "placementId": 12883451
- }
- }
- }
- ]
- },
- "expectedReturnCode": 400,
- "expectedErrorMessage": "Invalid request"
-}
-
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json b/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json
deleted file mode 100644
index 1fb7169fced..00000000000
--- a/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "description": "Invalid digitrust object in user extension",
- "mockBidRequest": {
- "id": "request-with-invalid-digitrust-obj",
- "site": {
- "page": "test.somepage.com"
- },
- "imp": [
- {
- "id": "my-imp-id",
- "banner": {
- "format": [
- {
- "w": 300,
- "h": 600
- }
- ]
- },
- "pmp": {
- "deals": [
- {
- "id": "some-deal-id"
- }
- ]
- },
- "ext": {
- "appnexus": {
- "placementId": 12883451
- }
- }
- }
- ],
- "user": {
- "yob": 1989,
- "ext": {
- "digitrust": {
- "id": "sample-digitrust-id",
- "keyv": 1,
- "pref": 1
- }
- }
- }
- },
- "expectedReturnCode": 400,
- "expectedErrorMessage": "Invalid request: request.user contains a digitrust object that is not valid.\n"
-}
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json
index 8385f924a56..5aa7fd4dea1 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json
@@ -39,5 +39,5 @@
]
},
"expectedReturnCode": 400,
- "expectedErrorMessage": "Invalid request: json: cannot unmarshal object into Go struct field ExtAppPrebid.prebid.source of type string"
+ "expectedErrorMessage": "Invalid request: json: cannot unmarshal object into Go struct field ExtAppPrebid.source of type string"
}
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
index afdabdab7cf..4a315911906 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
@@ -44,5 +44,5 @@
}
},
"expectedReturnCode": 400,
- "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go struct field ExtRegs.gdpr of type int8\n"
+ "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1.\n"
}
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json
index a8e94008cf1..ab44e3e2428 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json
@@ -42,5 +42,5 @@
}
},
"expectedReturnCode": 400,
- "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go value of type openrtb_ext.ExtRegs\n"
+ "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go value of type map[string]json.RawMessage\n"
}
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json
index b61be105df0..a26db8a5695 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json
@@ -46,5 +46,5 @@
}
},
"expectedReturnCode": 400,
- "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go struct field ExtUser.consent of type string\n"
+ "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string\n"
}
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json
index 08eed44b2b0..c4646550dd2 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json
@@ -41,5 +41,5 @@
}
},
"expectedReturnCode": 400,
- "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go struct field ExtUser.consent of type string"
+ "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string"
}
diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json
index 15af8551da6..e556b15d4f2 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json
@@ -23,19 +23,20 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur":"USD",
"nbr": 0,
- "seatbid": [
- {
- "bid": [
- {
- "id": "appnexus-bid",
- "impid": "",
- "price": 0
- }
- ],
- "seat": "appnexus-bids"
- }
- ]
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 0
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
},
"expectedReturnCode": 200
}
diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json
index 5d986bcf755..06673bcdf32 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json
@@ -23,19 +23,20 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur":"USD",
"nbr": 0,
- "seatbid": [
- {
- "bid": [
- {
- "id": "appnexus-bid",
- "impid": "",
- "price": 0
- }
- ],
- "seat": "appnexus-bids"
- }
- ]
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 0
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
},
"expectedReturnCode": 200
}
diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json
index 1e55cdda63f..9b8763491a3 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json
@@ -23,6 +23,7 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json
index 36a1745cb19..22ffc7f50d8 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json
@@ -23,6 +23,7 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json
index 98cdeedadbe..e60e2028637 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json
@@ -23,6 +23,7 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json
index dbf7b9c5e0d..a3b7101d8d5 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json
@@ -23,19 +23,20 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0,
- "seatbid": [
- {
- "bid": [
- {
- "id": "appnexus-bid",
- "impid": "",
- "price": 0
- }
- ],
- "seat": "appnexus-bids"
- }
- ]
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 0
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
},
"expectedReturnCode": 200
}
diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json
index 41fb833d770..77e8ce10a41 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json
@@ -23,19 +23,20 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0,
- "seatbid": [
- {
- "bid": [
- {
- "id": "appnexus-bid",
- "impid": "",
- "price": 0
- }
- ],
- "seat": "appnexus-bids"
- }
- ]
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 0
+ }
+ ],
+ "seat": "appnexus-bids"
+ }
+ ]
},
"expectedReturnCode": 200
}
diff --git a/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json
new file mode 100644
index 00000000000..214031177ca
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json
@@ -0,0 +1,36 @@
+{
+ "description": "Well formed native request with video asset using an exchange specific event tracker",
+ "mockBidRequest": {
+ "id": "req-id",
+ "site": {
+ "page": "some.page.com"
+ },
+ "tmax": 500,
+ "imp": [{
+ "id": "some-imp",
+ "native": {
+ "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}],\"eventtrackers\":[{\"event\":500,\"methods\":[1]}]}"
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 12883451
+ }
+ }
+ }]
+ },
+ "expectedBidResponse": {
+ "id": "req-id",
+ "bidid": "test bid id",
+ "nbr": 0,
+ "cur": "USD",
+ "seatbid": [{
+ "bid": [{
+ "id": "appnexus-bid",
+ "impid": "",
+ "price": 0
+ }],
+ "seat": "appnexus-bids"
+ }]
+ },
+ "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json
index 1ad97c8ff8f..5ebc4e697e4 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json
@@ -23,6 +23,7 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json
index 88af803684d..5518b7a06bc 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json
@@ -23,6 +23,7 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json
index ab192e14881..fcc7b72d62a 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json
@@ -23,6 +23,7 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json
index 0ec3c993251..f920c52a591 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json
@@ -23,6 +23,7 @@
"expectedBidResponse": {
"id": "req-id",
"bidid": "test bid id",
+ "cur": "USD",
"nbr": 0,
"seatbid": [
{
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json
index f875fa880bc..46af51635f9 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json
@@ -115,6 +115,7 @@
}
],
"bidid":"test bid id",
+ "cur":"USD",
"nbr":0
},
"expectedReturnCode": 200
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json
index 2c6a34f569e..d592cb66fcb 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json
@@ -44,6 +44,7 @@
}
],
"bidid": "test bid id",
+ "cur":"USD",
"nbr": 0
},
"expectedReturnCode": 200
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json
index e238f3c07c7..cb2cec992fe 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json
@@ -42,7 +42,8 @@
"seat": "appnexus-bids"
}],
"bidid": "test bid id",
+ "cur":"USD",
"nbr": 0
},
"expectedReturnCode": 200
-}
\ No newline at end of file
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json
deleted file mode 100644
index 5cd070745ab..00000000000
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
- "description": "Well formed amp request with digitrust extension that should run properly",
- "mockBidRequest": {
- "id": "request-with-valid-digitrust-obj",
- "site": {
- "page": "test.somepage.com"
- },
- "imp": [
- {
- "id": "my-imp-id",
- "banner": {
- "format": [
- {
- "w": 300,
- "h": 600
- }
- ]
- },
- "pmp": {
- "deals": [
- {
- "id": "some-deal-id"
- }
- ]
- },
- "ext": {
- "appnexus": {
- "placementId": 12883451
- }
- }
- }
- ],
- "user": {
- "yob": 1989,
- "ext": {
- "digitrust": {
- "id": "sample-digitrust-id",
- "keyv": 1,
- "pref": 0
- }
- }
- }
- },
- "expectedBidResponse": {
- "id":"request-with-valid-digitrust-obj",
- "bidid":"test bid id",
- "nbr":0
- },
- "expectedReturnCode": 200
-}
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 2ab3bbb0829..227f6c4a943 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -128,11 +128,13 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
cacheTTL = int64(deps.cfg.CacheURL.DefaultTTLs.Video)
}
debugLog := exchange.DebugLog{
- Enabled: strings.EqualFold(debugQuery, "true"),
- CacheType: prebid_cache_client.TypeXML,
- TTL: cacheTTL,
- Regexp: deps.debugLogRegexp,
+ Enabled: strings.EqualFold(debugQuery, "true"),
+ CacheType: prebid_cache_client.TypeXML,
+ TTL: cacheTTL,
+ Regexp: deps.debugLogRegexp,
+ DebugOverride: exchange.IsDebugOverrideEnabled(r.Header.Get(exchange.DebugOverrideHeader), deps.cfg.Debug.OverrideToken),
}
+ debugLog.DebugEnabledOrOverridden = debugLog.Enabled || debugLog.DebugOverride
defer func() {
if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil {
@@ -157,7 +159,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
}
resolvedRequest := requestJson
- if debugLog.Enabled {
+ if debugLog.DebugEnabledOrOverridden {
debugLog.Data.Request = string(requestJson)
if headerBytes, err := json.Marshal(r.Header); err == nil {
debugLog.Data.Headers = string(headerBytes)
@@ -209,7 +211,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
//create full open rtb req from full video request
mergeData(videoBidReq, bidReq)
// If debug query param is set, force the response to enable test flag
- if debugLog.Enabled {
+ if debugLog.DebugEnabledOrOverridden {
bidReq.Test = 1
}
@@ -239,7 +241,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
deps.setFieldsImplicitly(r, bidReq) // move after merge
- errL = deps.validateRequest(bidReq)
+ errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq})
if errortypes.ContainsFatalError(errL) {
handleError(&labels, w, errL, &vo, &debugLog)
return
@@ -274,13 +276,16 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
return
}
+ secGPC := r.Header.Get("Sec-GPC")
+
auctionRequest := exchange.AuctionRequest{
- BidRequest: bidReq,
- Account: *account,
- UserSyncs: usersyncs,
- RequestType: labels.RType,
- StartTime: start,
- LegacyLabels: labels,
+ BidRequest: bidReq,
+ Account: *account,
+ UserSyncs: usersyncs,
+ RequestType: labels.RType,
+ StartTime: start,
+ LegacyLabels: labels,
+ GlobalPrivacyControlHeader: secGPC,
}
response, err := deps.ex.HoldAuction(ctx, auctionRequest, &debugLog)
@@ -303,7 +308,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
bidResp.Ext = response.Ext
}
- if len(bidResp.AdPods) == 0 && debugLog.Enabled {
+ if len(bidResp.AdPods) == 0 && debugLog.DebugEnabledOrOverridden {
err := debugLog.PutDebugLogError(deps.cache, deps.cfg.CacheURL.ExpectedTimeMillis, vo.Errors)
if err != nil {
vo.Errors = append(vo.Errors, err)
@@ -341,7 +346,7 @@ func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []P
}
func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo *analytics.VideoObject, debugLog *exchange.DebugLog) {
- if debugLog != nil && debugLog.Enabled {
+ if debugLog != nil && debugLog.DebugEnabledOrOverridden {
if rawUUID, err := uuid.NewV4(); err == nil {
debugLog.CacheKey = rawUUID.String()
}
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 9ede7147686..5452d6c2c39 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -1041,8 +1041,10 @@ func TestHandleErrorDebugLog(t *testing.T) {
Headers: "test headers string",
Response: "test response string",
},
- TTL: int64(3600),
- Regexp: regexp.MustCompile(`[<>]`),
+ TTL: int64(3600),
+ Regexp: regexp.MustCompile(`[<>]`),
+ DebugOverride: false,
+ DebugEnabledOrOverridden: true,
}
handleError(&labels, recorder, []error{err1, err2}, &vo, &debugLog)
@@ -1085,11 +1087,13 @@ func TestCCPA(t *testing.T) {
description string
testFilePath string
expectConsentString bool
+ expectEmptyConsent bool
}{
{
description: "Missing Consent",
testFilePath: "sample-requests/video/video_valid_sample.json",
expectConsentString: false,
+ expectEmptyConsent: true,
},
{
description: "Valid Consent",
@@ -1130,7 +1134,7 @@ func TestCCPA(t *testing.T) {
}
if test.expectConsentString {
assert.Len(t, extRegs.USPrivacy, 4, test.description+":consent")
- } else {
+ } else if test.expectEmptyConsent {
assert.Empty(t, extRegs.USPrivacy, test.description+":consent")
}
diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go
index 0d68c15bea8..caeb09a858c 100644
--- a/endpoints/setuid_test.go
+++ b/endpoints/setuid_test.go
@@ -389,9 +389,9 @@ func makeRequest(uri string, existingSyncs map[string]string) *http.Request {
func doRequest(req *http.Request, metrics metrics.MetricsEngine, validFamilyNames []string, gdprAllowsHostCookies bool, gdprReturnsError bool) *httptest.ResponseRecorder {
cfg := config.Configuration{}
perms := &mockPermsSetUID{
- allowHost: gdprAllowsHostCookies,
- errorHost: gdprReturnsError,
- allowPI: true,
+ allowHost: gdprAllowsHostCookies,
+ errorHost: gdprReturnsError,
+ personalInfoAllowed: true,
}
analytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics)
syncers := make(map[openrtb_ext.BidderName]usersync.Usersyncer)
@@ -422,9 +422,9 @@ func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *users
}
type mockPermsSetUID struct {
- allowHost bool
- errorHost bool
- allowPI bool
+ allowHost bool
+ errorHost bool
+ personalInfoAllowed bool
}
func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) {
@@ -439,8 +439,8 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_
return false, nil
}
-func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) {
- return g.allowPI, g.allowPI, g.allowPI, nil
+func (g *mockPermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) {
+ return g.personalInfoAllowed, g.personalInfoAllowed, g.personalInfoAllowed, nil
}
func newFakeSyncer(familyName string) usersync.Usersyncer {
diff --git a/errortypes/code.go b/errortypes/code.go
index f8206525b27..554357ea88a 100644
--- a/errortypes/code.go
+++ b/errortypes/code.go
@@ -11,6 +11,7 @@ const (
BidderTemporarilyDisabledErrorCode
BlacklistedAcctErrorCode
AcctRequiredErrorCode
+ NoConversionRateErrorCode
NoBidPriceErrorCode
)
@@ -20,6 +21,7 @@ const (
InvalidPrivacyConsentWarningCode = iota + 10000
AccountLevelDebugDisabledWarningCode
BidderLevelDebugDisabledWarningCode
+ DisabledCurrencyConversionWarningCode
)
// Coder provides an error or warning code with severity.
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 6ac494448ca..b24fde3020e 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -4,6 +4,8 @@ import (
"github.com/prebid/prebid-server/adapters"
ttx "github.com/prebid/prebid-server/adapters/33across"
"github.com/prebid/prebid-server/adapters/acuityads"
+ "github.com/prebid/prebid-server/adapters/adagio"
+ "github.com/prebid/prebid-server/adapters/adf"
"github.com/prebid/prebid-server/adapters/adform"
"github.com/prebid/prebid-server/adapters/adgeneration"
"github.com/prebid/prebid-server/adapters/adhese"
@@ -22,15 +24,20 @@ import (
"github.com/prebid/prebid-server/adapters/adxcg"
"github.com/prebid/prebid-server/adapters/adyoulike"
"github.com/prebid/prebid-server/adapters/aja"
+ "github.com/prebid/prebid-server/adapters/algorix"
"github.com/prebid/prebid-server/adapters/amx"
"github.com/prebid/prebid-server/adapters/applogy"
"github.com/prebid/prebid-server/adapters/appnexus"
"github.com/prebid/prebid-server/adapters/audienceNetwork"
"github.com/prebid/prebid-server/adapters/avocet"
+ "github.com/prebid/prebid-server/adapters/axonix"
"github.com/prebid/prebid-server/adapters/beachfront"
"github.com/prebid/prebid-server/adapters/beintoo"
"github.com/prebid/prebid-server/adapters/between"
"github.com/prebid/prebid-server/adapters/bidmachine"
+ "github.com/prebid/prebid-server/adapters/bidmyadz"
+ "github.com/prebid/prebid-server/adapters/bidscube"
+ "github.com/prebid/prebid-server/adapters/bmtm"
"github.com/prebid/prebid-server/adapters/brightroll"
"github.com/prebid/prebid-server/adapters/colossus"
"github.com/prebid/prebid-server/adapters/connectad"
@@ -42,6 +49,7 @@ import (
"github.com/prebid/prebid-server/adapters/decenterads"
"github.com/prebid/prebid-server/adapters/deepintent"
"github.com/prebid/prebid-server/adapters/dmx"
+ "github.com/prebid/prebid-server/adapters/e_volution"
"github.com/prebid/prebid-server/adapters/emx_digital"
"github.com/prebid/prebid-server/adapters/engagebdr"
"github.com/prebid/prebid-server/adapters/eplanning"
@@ -52,15 +60,18 @@ import (
"github.com/prebid/prebid-server/adapters/gumgum"
"github.com/prebid/prebid-server/adapters/improvedigital"
"github.com/prebid/prebid-server/adapters/inmobi"
+ "github.com/prebid/prebid-server/adapters/interactiveoffers"
"github.com/prebid/prebid-server/adapters/invibes"
"github.com/prebid/prebid-server/adapters/ix"
"github.com/prebid/prebid-server/adapters/jixie"
+ "github.com/prebid/prebid-server/adapters/kayzen"
"github.com/prebid/prebid-server/adapters/kidoz"
"github.com/prebid/prebid-server/adapters/krushmedia"
"github.com/prebid/prebid-server/adapters/kubient"
"github.com/prebid/prebid-server/adapters/lockerdome"
"github.com/prebid/prebid-server/adapters/logicad"
"github.com/prebid/prebid-server/adapters/lunamedia"
+ "github.com/prebid/prebid-server/adapters/madvertise"
"github.com/prebid/prebid-server/adapters/marsmedia"
"github.com/prebid/prebid-server/adapters/mgid"
"github.com/prebid/prebid-server/adapters/mobfoxpb"
@@ -70,6 +81,7 @@ import (
"github.com/prebid/prebid-server/adapters/nobid"
"github.com/prebid/prebid-server/adapters/onetag"
"github.com/prebid/prebid-server/adapters/openx"
+ "github.com/prebid/prebid-server/adapters/operaads"
"github.com/prebid/prebid-server/adapters/orbidder"
"github.com/prebid/prebid-server/adapters/outbrain"
"github.com/prebid/prebid-server/adapters/pangle"
@@ -80,12 +92,14 @@ import (
"github.com/prebid/prebid-server/adapters/rhythmone"
"github.com/prebid/prebid-server/adapters/rtbhouse"
"github.com/prebid/prebid-server/adapters/rubicon"
+ "github.com/prebid/prebid-server/adapters/sa_lunamedia"
"github.com/prebid/prebid-server/adapters/sharethrough"
"github.com/prebid/prebid-server/adapters/silvermob"
"github.com/prebid/prebid-server/adapters/smaato"
"github.com/prebid/prebid-server/adapters/smartadserver"
"github.com/prebid/prebid-server/adapters/smartrtb"
"github.com/prebid/prebid-server/adapters/smartyads"
+ "github.com/prebid/prebid-server/adapters/smilewanted"
"github.com/prebid/prebid-server/adapters/somoaudience"
"github.com/prebid/prebid-server/adapters/sonobi"
"github.com/prebid/prebid-server/adapters/sovrn"
@@ -116,113 +130,128 @@ import (
func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
return map[openrtb_ext.BidderName]adapters.Builder{
- openrtb_ext.Bidder33Across: ttx.Builder,
- openrtb_ext.BidderAcuityAds: acuityads.Builder,
- openrtb_ext.BidderAdform: adform.Builder,
- openrtb_ext.BidderAdgeneration: adgeneration.Builder,
- openrtb_ext.BidderAdhese: adhese.Builder,
- openrtb_ext.BidderAdkernel: adkernel.Builder,
- openrtb_ext.BidderAdkernelAdn: adkernelAdn.Builder,
- openrtb_ext.BidderAdman: adman.Builder,
- openrtb_ext.BidderAdmixer: admixer.Builder,
- openrtb_ext.BidderAdOcean: adocean.Builder,
- openrtb_ext.BidderAdoppler: adoppler.Builder,
- openrtb_ext.BidderAdpone: adpone.Builder,
- openrtb_ext.BidderAdot: adot.Builder,
- openrtb_ext.BidderAdprime: adprime.Builder,
- openrtb_ext.BidderAdtarget: adtarget.Builder,
- openrtb_ext.BidderAdtelligent: adtelligent.Builder,
- openrtb_ext.BidderAdvangelists: advangelists.Builder,
- openrtb_ext.BidderAdxcg: adxcg.Builder,
- openrtb_ext.BidderAdyoulike: adyoulike.Builder,
- openrtb_ext.BidderAJA: aja.Builder,
- openrtb_ext.BidderAMX: amx.Builder,
- openrtb_ext.BidderApplogy: applogy.Builder,
- openrtb_ext.BidderAppnexus: appnexus.Builder,
- openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder,
- openrtb_ext.BidderAvocet: avocet.Builder,
- openrtb_ext.BidderBeachfront: beachfront.Builder,
- openrtb_ext.BidderBeintoo: beintoo.Builder,
- openrtb_ext.BidderBetween: between.Builder,
- openrtb_ext.BidderBidmachine: bidmachine.Builder,
- openrtb_ext.BidderBrightroll: brightroll.Builder,
- openrtb_ext.BidderColossus: colossus.Builder,
- openrtb_ext.BidderConnectAd: connectad.Builder,
- openrtb_ext.BidderConsumable: consumable.Builder,
- openrtb_ext.BidderConversant: conversant.Builder,
- openrtb_ext.BidderCpmstar: cpmstar.Builder,
- openrtb_ext.BidderCriteo: criteo.Builder,
- openrtb_ext.BidderDatablocks: datablocks.Builder,
- openrtb_ext.BidderDecenterAds: decenterads.Builder,
- openrtb_ext.BidderDeepintent: deepintent.Builder,
- openrtb_ext.BidderDmx: dmx.Builder,
- openrtb_ext.BidderEmxDigital: emx_digital.Builder,
- openrtb_ext.BidderEngageBDR: engagebdr.Builder,
- openrtb_ext.BidderEPlanning: eplanning.Builder,
- openrtb_ext.BidderEpom: epom.Builder,
- openrtb_ext.BidderGamma: gamma.Builder,
- openrtb_ext.BidderGamoshi: gamoshi.Builder,
- openrtb_ext.BidderGrid: grid.Builder,
- openrtb_ext.BidderGumGum: gumgum.Builder,
- openrtb_ext.BidderImprovedigital: improvedigital.Builder,
- openrtb_ext.BidderInMobi: inmobi.Builder,
- openrtb_ext.BidderInvibes: invibes.Builder,
- openrtb_ext.BidderIx: ix.Builder,
- openrtb_ext.BidderJixie: jixie.Builder,
- openrtb_ext.BidderKidoz: kidoz.Builder,
- openrtb_ext.BidderKrushmedia: krushmedia.Builder,
- openrtb_ext.BidderKubient: kubient.Builder,
- openrtb_ext.BidderLockerDome: lockerdome.Builder,
- openrtb_ext.BidderLogicad: logicad.Builder,
- openrtb_ext.BidderLunaMedia: lunamedia.Builder,
- openrtb_ext.BidderMarsmedia: marsmedia.Builder,
- openrtb_ext.BidderMediafuse: adtelligent.Builder,
- openrtb_ext.BidderMgid: mgid.Builder,
- openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder,
- openrtb_ext.BidderMobileFuse: mobilefuse.Builder,
- openrtb_ext.BidderNanoInteractive: nanointeractive.Builder,
- openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder,
- openrtb_ext.BidderNoBid: nobid.Builder,
- openrtb_ext.BidderOneTag: onetag.Builder,
- openrtb_ext.BidderOpenx: openx.Builder,
- openrtb_ext.BidderOrbidder: orbidder.Builder,
- openrtb_ext.BidderOutbrain: outbrain.Builder,
- openrtb_ext.BidderPangle: pangle.Builder,
- openrtb_ext.BidderPubmatic: pubmatic.Builder,
- openrtb_ext.BidderPubnative: pubnative.Builder,
- openrtb_ext.BidderPulsepoint: pulsepoint.Builder,
- openrtb_ext.BidderRevcontent: revcontent.Builder,
- openrtb_ext.BidderRhythmone: rhythmone.Builder,
- openrtb_ext.BidderRTBHouse: rtbhouse.Builder,
- openrtb_ext.BidderRubicon: rubicon.Builder,
- openrtb_ext.BidderSharethrough: sharethrough.Builder,
- openrtb_ext.BidderSilverMob: silvermob.Builder,
- openrtb_ext.BidderSmaato: smaato.Builder,
- openrtb_ext.BidderSmartAdserver: smartadserver.Builder,
- openrtb_ext.BidderSmartRTB: smartrtb.Builder,
- openrtb_ext.BidderSmartyAds: smartyads.Builder,
- openrtb_ext.BidderSomoaudience: somoaudience.Builder,
- openrtb_ext.BidderSonobi: sonobi.Builder,
- openrtb_ext.BidderSovrn: sovrn.Builder,
- openrtb_ext.BidderSpotX: spotx.Builder,
- openrtb_ext.BidderSynacormedia: synacormedia.Builder,
- openrtb_ext.BidderTappx: tappx.Builder,
- openrtb_ext.BidderTelaria: telaria.Builder,
- openrtb_ext.BidderTriplelift: triplelift.Builder,
- openrtb_ext.BidderTripleliftNative: triplelift_native.Builder,
- openrtb_ext.BidderTrustX: grid.Builder,
- openrtb_ext.BidderUcfunnel: ucfunnel.Builder,
- openrtb_ext.BidderUnicorn: unicorn.Builder,
- openrtb_ext.BidderUnruly: unruly.Builder,
- openrtb_ext.BidderValueImpression: valueimpression.Builder,
- openrtb_ext.BidderVASTBidder: vastbidder.Builder,
- openrtb_ext.BidderVerizonMedia: verizonmedia.Builder,
- openrtb_ext.BidderVisx: visx.Builder,
- openrtb_ext.BidderVrtcal: vrtcal.Builder,
- openrtb_ext.BidderYeahmobi: yeahmobi.Builder,
- openrtb_ext.BidderYieldlab: yieldlab.Builder,
- openrtb_ext.BidderYieldmo: yieldmo.Builder,
- openrtb_ext.BidderYieldone: yieldone.Builder,
- openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder,
+ openrtb_ext.Bidder33Across: ttx.Builder,
+ openrtb_ext.BidderAcuityAds: acuityads.Builder,
+ openrtb_ext.BidderAdagio: adagio.Builder,
+ openrtb_ext.BidderAdf: adf.Builder,
+ openrtb_ext.BidderAdform: adform.Builder,
+ openrtb_ext.BidderAdgeneration: adgeneration.Builder,
+ openrtb_ext.BidderAdhese: adhese.Builder,
+ openrtb_ext.BidderAdkernel: adkernel.Builder,
+ openrtb_ext.BidderAdkernelAdn: adkernelAdn.Builder,
+ openrtb_ext.BidderAdman: adman.Builder,
+ openrtb_ext.BidderAdmixer: admixer.Builder,
+ openrtb_ext.BidderAdOcean: adocean.Builder,
+ openrtb_ext.BidderAdoppler: adoppler.Builder,
+ openrtb_ext.BidderAdpone: adpone.Builder,
+ openrtb_ext.BidderAdot: adot.Builder,
+ openrtb_ext.BidderAdprime: adprime.Builder,
+ openrtb_ext.BidderAdtarget: adtarget.Builder,
+ openrtb_ext.BidderAdtelligent: adtelligent.Builder,
+ openrtb_ext.BidderAdvangelists: advangelists.Builder,
+ openrtb_ext.BidderAdxcg: adxcg.Builder,
+ openrtb_ext.BidderAdyoulike: adyoulike.Builder,
+ openrtb_ext.BidderAJA: aja.Builder,
+ openrtb_ext.BidderAlgorix: algorix.Builder,
+ openrtb_ext.BidderAMX: amx.Builder,
+ openrtb_ext.BidderApplogy: applogy.Builder,
+ openrtb_ext.BidderAppnexus: appnexus.Builder,
+ openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder,
+ openrtb_ext.BidderAvocet: avocet.Builder,
+ openrtb_ext.BidderAxonix: axonix.Builder,
+ openrtb_ext.BidderBeachfront: beachfront.Builder,
+ openrtb_ext.BidderBeintoo: beintoo.Builder,
+ openrtb_ext.BidderBetween: between.Builder,
+ openrtb_ext.BidderBidmachine: bidmachine.Builder,
+ openrtb_ext.BidderBidmyadz: bidmyadz.Builder,
+ openrtb_ext.BidderBidsCube: bidscube.Builder,
+ openrtb_ext.BidderBmtm: bmtm.Builder,
+ openrtb_ext.BidderBrightroll: brightroll.Builder,
+ openrtb_ext.BidderColossus: colossus.Builder,
+ openrtb_ext.BidderConnectAd: connectad.Builder,
+ openrtb_ext.BidderConsumable: consumable.Builder,
+ openrtb_ext.BidderConversant: conversant.Builder,
+ openrtb_ext.BidderCpmstar: cpmstar.Builder,
+ openrtb_ext.BidderCriteo: criteo.Builder,
+ openrtb_ext.BidderDatablocks: datablocks.Builder,
+ openrtb_ext.BidderDecenterAds: decenterads.Builder,
+ openrtb_ext.BidderDeepintent: deepintent.Builder,
+ openrtb_ext.BidderDmx: dmx.Builder,
+ openrtb_ext.BidderEmxDigital: emx_digital.Builder,
+ openrtb_ext.BidderEngageBDR: engagebdr.Builder,
+ openrtb_ext.BidderEPlanning: eplanning.Builder,
+ openrtb_ext.BidderEpom: epom.Builder,
+ openrtb_ext.BidderEVolution: evolution.Builder,
+ openrtb_ext.BidderGamma: gamma.Builder,
+ openrtb_ext.BidderGamoshi: gamoshi.Builder,
+ openrtb_ext.BidderGrid: grid.Builder,
+ openrtb_ext.BidderGumGum: gumgum.Builder,
+ openrtb_ext.BidderImprovedigital: improvedigital.Builder,
+ openrtb_ext.BidderInMobi: inmobi.Builder,
+ openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder,
+ openrtb_ext.BidderInvibes: invibes.Builder,
+ openrtb_ext.BidderIx: ix.Builder,
+ openrtb_ext.BidderJixie: jixie.Builder,
+ openrtb_ext.BidderKayzen: kayzen.Builder,
+ openrtb_ext.BidderKidoz: kidoz.Builder,
+ openrtb_ext.BidderKrushmedia: krushmedia.Builder,
+ openrtb_ext.BidderKubient: kubient.Builder,
+ openrtb_ext.BidderLockerDome: lockerdome.Builder,
+ openrtb_ext.BidderLogicad: logicad.Builder,
+ openrtb_ext.BidderLunaMedia: lunamedia.Builder,
+ openrtb_ext.BidderSaLunaMedia: salunamedia.Builder,
+ openrtb_ext.BidderMadvertise: madvertise.Builder,
+ openrtb_ext.BidderMarsmedia: marsmedia.Builder,
+ openrtb_ext.BidderMediafuse: adtelligent.Builder,
+ openrtb_ext.BidderMgid: mgid.Builder,
+ openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder,
+ openrtb_ext.BidderMobileFuse: mobilefuse.Builder,
+ openrtb_ext.BidderNanoInteractive: nanointeractive.Builder,
+ openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder,
+ openrtb_ext.BidderNoBid: nobid.Builder,
+ openrtb_ext.BidderOneTag: onetag.Builder,
+ openrtb_ext.BidderOpenx: openx.Builder,
+ openrtb_ext.BidderOperaads: operaads.Builder,
+ openrtb_ext.BidderOrbidder: orbidder.Builder,
+ openrtb_ext.BidderOutbrain: outbrain.Builder,
+ openrtb_ext.BidderPangle: pangle.Builder,
+ openrtb_ext.BidderPubmatic: pubmatic.Builder,
+ openrtb_ext.BidderPubnative: pubnative.Builder,
+ openrtb_ext.BidderPulsepoint: pulsepoint.Builder,
+ openrtb_ext.BidderRevcontent: revcontent.Builder,
+ openrtb_ext.BidderRhythmone: rhythmone.Builder,
+ openrtb_ext.BidderRTBHouse: rtbhouse.Builder,
+ openrtb_ext.BidderRubicon: rubicon.Builder,
+ openrtb_ext.BidderSharethrough: sharethrough.Builder,
+ openrtb_ext.BidderSilverMob: silvermob.Builder,
+ openrtb_ext.BidderSmaato: smaato.Builder,
+ openrtb_ext.BidderSmartAdserver: smartadserver.Builder,
+ openrtb_ext.BidderSmartRTB: smartrtb.Builder,
+ openrtb_ext.BidderSmartyAds: smartyads.Builder,
+ openrtb_ext.BidderSmileWanted: smilewanted.Builder,
+ openrtb_ext.BidderSomoaudience: somoaudience.Builder,
+ openrtb_ext.BidderSonobi: sonobi.Builder,
+ openrtb_ext.BidderSovrn: sovrn.Builder,
+ openrtb_ext.BidderSpotX: spotx.Builder,
+ openrtb_ext.BidderSynacormedia: synacormedia.Builder,
+ openrtb_ext.BidderTappx: tappx.Builder,
+ openrtb_ext.BidderTelaria: telaria.Builder,
+ openrtb_ext.BidderTriplelift: triplelift.Builder,
+ openrtb_ext.BidderTripleliftNative: triplelift_native.Builder,
+ openrtb_ext.BidderTrustX: grid.Builder,
+ openrtb_ext.BidderUcfunnel: ucfunnel.Builder,
+ openrtb_ext.BidderUnicorn: unicorn.Builder,
+ openrtb_ext.BidderUnruly: unruly.Builder,
+ openrtb_ext.BidderValueImpression: valueimpression.Builder,
+ openrtb_ext.BidderVASTBidder: vastbidder.Builder,
+ openrtb_ext.BidderVerizonMedia: verizonmedia.Builder,
+ openrtb_ext.BidderViewdeos: adtelligent.Builder,
+ openrtb_ext.BidderVisx: visx.Builder,
+ openrtb_ext.BidderVrtcal: vrtcal.Builder,
+ openrtb_ext.BidderYeahmobi: yeahmobi.Builder,
+ openrtb_ext.BidderYieldlab: yieldlab.Builder,
+ openrtb_ext.BidderYieldmo: yieldmo.Builder,
+ openrtb_ext.BidderYieldone: yieldone.Builder,
+ openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder,
}
}
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index 8af6d11ad60..9ab27561de0 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -4,33 +4,13 @@ import (
"fmt"
"net/http"
- "github.com/prebid/prebid-server/metrics"
-
"github.com/prebid/prebid-server/adapters"
- "github.com/prebid/prebid-server/adapters/lifestreet"
"github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/metrics"
"github.com/prebid/prebid-server/openrtb_ext"
)
func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]adaptedBidder, []error) {
- exchangeBidders := buildExchangeBiddersLegacy(cfg.Adapters, infos)
-
- exchangeBiddersModern, errs := buildExchangeBidders(cfg, infos, client, me)
- if len(errs) > 0 {
- return nil, errs
- }
-
- // Merge legacy and modern bidders, giving priority to the modern bidders.
- for bidderName, bidder := range exchangeBiddersModern {
- exchangeBidders[bidderName] = bidder
- }
-
- wrapWithMiddleware(exchangeBidders)
-
- return exchangeBidders, nil
-}
-
-func buildExchangeBidders(cfg *config.Configuration, infos config.BidderInfos, client *http.Client, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]adaptedBidder, []error) {
bidders, errs := buildBidders(cfg.Adapters, infos, newAdapterBuilders())
if len(errs) > 0 {
return nil, errs
@@ -38,16 +18,12 @@ func buildExchangeBidders(cfg *config.Configuration, infos config.BidderInfos, c
exchangeBidders := make(map[openrtb_ext.BidderName]adaptedBidder, len(bidders))
for bidderName, bidder := range bidders {
- info, infoFound := infos[string(bidderName)]
- if !infoFound {
- errs = append(errs, fmt.Errorf("%v: bidder info not found", bidder))
- continue
- }
- exchangeBidders[bidderName] = adaptBidder(bidder, client, cfg, me, bidderName, info.Debug)
+ info := infos[string(bidderName)]
+ exchangeBidder := adaptBidder(bidder, client, cfg, me, bidderName, info.Debug)
+ exchangeBidder = addValidatedBidderMiddleware(exchangeBidder)
+ exchangeBidders[bidderName] = exchangeBidder
}
-
return exchangeBidders, nil
-
}
func buildBidders(adapterConfig map[string]config.Adapter, infos config.BidderInfos, builders map[openrtb_ext.BidderName]adapters.Builder) (map[openrtb_ext.BidderName]adapters.Bidder, []error) {
@@ -61,11 +37,6 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos config.BidderIn
continue
}
- // Ignore Legacy Bidders
- if bidderName == openrtb_ext.BidderLifestreet {
- continue
- }
-
info, infoFound := infos[string(bidderName)]
if !infoFound {
errs = append(errs, fmt.Errorf("%v: bidder info not found", bidder))
@@ -84,34 +55,13 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos config.BidderIn
errs = append(errs, fmt.Errorf("%v: %v", bidder, builderErr))
continue
}
-
- bidderWithInfoEnforcement := adapters.BuildInfoAwareBidder(bidderInstance, info)
-
- bidders[bidderName] = bidderWithInfoEnforcement
+ bidders[bidderName] = adapters.BuildInfoAwareBidder(bidderInstance, info)
}
}
return bidders, errs
}
-func buildExchangeBiddersLegacy(adapterConfig map[string]config.Adapter, infos config.BidderInfos) map[openrtb_ext.BidderName]adaptedBidder {
- bidders := make(map[openrtb_ext.BidderName]adaptedBidder, 2)
-
- // Lifestreet
- if infos[string(openrtb_ext.BidderLifestreet)].Enabled {
- adapter := lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, adapterConfig[string(openrtb_ext.BidderLifestreet)].Endpoint)
- bidders[openrtb_ext.BidderLifestreet] = adaptLegacyAdapter(adapter)
- }
-
- return bidders
-}
-
-func wrapWithMiddleware(bidders map[openrtb_ext.BidderName]adaptedBidder) {
- for name, bidder := range bidders {
- bidders[name] = addValidatedBidderMiddleware(bidder)
- }
-}
-
// GetActiveBidders returns a map of all active bidder names.
func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderName {
activeBidders := make(map[string]openrtb_ext.BidderName)
@@ -127,7 +77,9 @@ func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderNam
// GetDisabledBiddersErrorMessages returns a map of error messages for disabled bidders.
func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string {
- disabledBidders := make(map[string]string)
+ disabledBidders := map[string]string{
+ "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+ }
for name, info := range infos {
if !info.Enabled {
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index c9f1907d314..a8e8ef5e30a 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -1,7 +1,6 @@
package exchange
import (
- "context"
"errors"
"net/http"
"testing"
@@ -9,10 +8,8 @@ import (
"github.com/mxmCherry/openrtb/v15/openrtb2"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/adapters/appnexus"
- "github.com/prebid/prebid-server/adapters/lifestreet"
"github.com/prebid/prebid-server/adapters/rubicon"
"github.com/prebid/prebid-server/config"
- "github.com/prebid/prebid-server/currency"
metrics "github.com/prebid/prebid-server/metrics/config"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
@@ -23,64 +20,19 @@ var (
infoDisabled = config.BidderInfo{Enabled: false}
)
-func TestBuildAdaptersSuccess(t *testing.T) {
- client := &http.Client{}
- cfg := &config.Configuration{Adapters: map[string]config.Adapter{
- "appnexus": {},
- "lifestreet": {Endpoint: "anyEndpoint"},
- }}
- infos := map[string]config.BidderInfo{
- "appnexus": infoEnabled,
- "lifestreet": infoEnabled,
- }
- metricEngine := &metrics.DummyMetricsEngine{}
-
- bidders, errs := BuildAdapters(client, cfg, infos, metricEngine)
-
- appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{})
- appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled)
- appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil)
- appnexusBidderValidated := addValidatedBidderMiddleware(appnexusBidderAdapted)
-
- idLegacyAdapted := &adaptedAdapter{lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")}
- idLegacyValidated := addValidatedBidderMiddleware(idLegacyAdapted)
-
- expectedBidders := map[openrtb_ext.BidderName]adaptedBidder{
- openrtb_ext.BidderAppnexus: appnexusBidderValidated,
- openrtb_ext.BidderLifestreet: idLegacyValidated,
- }
-
- assert.Equal(t, expectedBidders, bidders)
- assert.Empty(t, errs)
-}
-
-func TestBuildAdaptersErrors(t *testing.T) {
- client := &http.Client{}
- cfg := &config.Configuration{Adapters: map[string]config.Adapter{"unknown": {}}}
- infos := map[string]config.BidderInfo{}
- metricEngine := &metrics.DummyMetricsEngine{}
-
- bidders, errs := BuildAdapters(client, cfg, infos, metricEngine)
-
- expectedErrors := []error{
- errors.New("unknown: unknown bidder"),
- }
-
- assert.Empty(t, bidders)
- assert.Equal(t, expectedErrors, errs)
-}
-
-func TestBuildExchangeBidders(t *testing.T) {
+func TestBuildAdapters(t *testing.T) {
client := &http.Client{}
metricEngine := &metrics.DummyMetricsEngine{}
appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{})
appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled)
appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil)
+ appnexusValidated := addValidatedBidderMiddleware(appnexusBidderAdapted)
rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{})
rubiconBidderWithInfo := adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled)
rubiconBidderAdapted := adaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil)
+ rubiconbidderValidated := addValidatedBidderMiddleware(rubiconBidderAdapted)
testCases := []struct {
description string
@@ -90,42 +42,42 @@ func TestBuildExchangeBidders(t *testing.T) {
expectedErrors []error
}{
{
- description: "Invalid - Builder Errors",
- adapterConfig: map[string]config.Adapter{"appnexus": {}, "unknown": {}},
- bidderInfos: map[string]config.BidderInfo{},
- expectedErrors: []error{
- errors.New("appnexus: bidder info not found"),
- errors.New("unknown: unknown bidder"),
- },
- },
- {
- description: "Success - None",
+ description: "No Bidders",
adapterConfig: map[string]config.Adapter{},
bidderInfos: map[string]config.BidderInfo{},
expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{},
},
{
- description: "Success - One",
+ description: "One Bidder",
adapterConfig: map[string]config.Adapter{"appnexus": {}},
bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled},
expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{
- openrtb_ext.BidderAppnexus: appnexusBidderAdapted,
+ openrtb_ext.BidderAppnexus: appnexusValidated,
},
},
{
- description: "Success - Many",
+ description: "Many Bidders",
adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}},
bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "rubicon": infoEnabled},
expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{
- openrtb_ext.BidderAppnexus: appnexusBidderAdapted,
- openrtb_ext.BidderRubicon: rubiconBidderAdapted,
+ openrtb_ext.BidderAppnexus: appnexusValidated,
+ openrtb_ext.BidderRubicon: rubiconbidderValidated,
+ },
+ },
+ {
+ description: "Invalid - Builder Errors",
+ adapterConfig: map[string]config.Adapter{"appnexus": {}, "unknown": {}},
+ bidderInfos: map[string]config.BidderInfo{},
+ expectedErrors: []error{
+ errors.New("appnexus: bidder info not found"),
+ errors.New("unknown: unknown bidder"),
},
},
}
for _, test := range testCases {
cfg := &config.Configuration{Adapters: test.adapterConfig}
- bidders, errs := buildExchangeBidders(cfg, test.bidderInfos, client, metricEngine)
+ bidders, errs := BuildAdapters(client, cfg, test.bidderInfos, metricEngine)
assert.Equal(t, test.expectedBidders, bidders, test.description+":bidders")
assert.ElementsMatch(t, test.expectedErrors, errs, test.description+":errors")
}
@@ -139,8 +91,6 @@ func TestBuildBidders(t *testing.T) {
rubiconBidder := fakeBidder{"b"}
rubiconBuilder := fakeBuilder{rubiconBidder, nil}.Builder
- inconsequentialBuilder := fakeBuilder{fakeBidder{"whatevs"}, nil}.Builder
-
testCases := []struct {
description string
adapterConfig map[string]config.Adapter
@@ -210,15 +160,6 @@ func TestBuildBidders(t *testing.T) {
openrtb_ext.BidderRubicon: adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled),
},
},
- {
- description: "Success - Ignores Legacy",
- adapterConfig: map[string]config.Adapter{"appnexus": {}, "lifestreet": {}},
- bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "lifestreet": infoEnabled},
- builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderLifestreet: inconsequentialBuilder},
- expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{
- openrtb_ext.BidderAppnexus: adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled),
- },
- },
{
description: "Success - Ignores Disabled",
adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}},
@@ -252,53 +193,6 @@ func TestBuildBidders(t *testing.T) {
}
}
-func TestBuildExchangeBiddersLegacy(t *testing.T) {
- cfg := config.Adapter{Endpoint: "anyEndpoint"}
-
- expectedLifestreet := &adaptedAdapter{lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")}
-
- testCases := []struct {
- description string
- adapterConfig map[string]config.Adapter
- bidderInfos map[string]config.BidderInfo
- expected map[openrtb_ext.BidderName]adaptedBidder
- }{
- {
- description: "Active",
- adapterConfig: map[string]config.Adapter{"lifestreet": cfg},
- bidderInfos: map[string]config.BidderInfo{"lifestreet": infoEnabled},
- expected: map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet},
- },
- {
- description: "Disabled",
- adapterConfig: map[string]config.Adapter{"lifestreet": cfg},
- bidderInfos: map[string]config.BidderInfo{"lifestreet": infoDisabled},
- expected: map[openrtb_ext.BidderName]adaptedBidder{},
- },
- }
-
- for _, test := range testCases {
- result := buildExchangeBiddersLegacy(test.adapterConfig, test.bidderInfos)
- assert.Equal(t, test.expected, result, test.description)
- }
-}
-
-func TestWrapWithMiddleware(t *testing.T) {
- appNexusBidder := fakeAdaptedBidder{}
-
- bidders := map[openrtb_ext.BidderName]adaptedBidder{
- openrtb_ext.BidderAppnexus: appNexusBidder,
- }
-
- wrapWithMiddleware(bidders)
-
- expected := map[openrtb_ext.BidderName]adaptedBidder{
- openrtb_ext.BidderAppnexus: &validatedBidder{appNexusBidder},
- }
-
- assert.Equal(t, expected, bidders)
-}
-
func TestGetActiveBidders(t *testing.T) {
testCases := []struct {
description string
@@ -342,24 +236,32 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
{
description: "None",
bidderInfos: map[string]config.BidderInfo{},
- expected: map[string]string{},
+ expected: map[string]string{
+ "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+ },
},
{
description: "Enabled",
bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled},
- expected: map[string]string{},
+ expected: map[string]string{
+ "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+ },
},
{
description: "Disabled",
bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled},
expected: map[string]string{
- "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
+ "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+ "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
},
},
{
description: "Mixed",
bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "openx": infoEnabled},
- expected: map[string]string{"appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`},
+ expected: map[string]string{
+ "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+ "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
+ },
},
}
@@ -369,12 +271,6 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
}
}
-type fakeAdaptedBidder struct{}
-
-func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
- return nil, nil
-}
-
type fakeBidder struct {
name string
}
diff --git a/exchange/auction.go b/exchange/auction.go
index f2c37f7a8bd..2b5d6f75aeb 100644
--- a/exchange/auction.go
+++ b/exchange/auction.go
@@ -17,14 +17,21 @@ import (
"github.com/prebid/prebid-server/prebid_cache_client"
)
+const (
+ DebugOverrideHeader string = "x-pbs-debug-override"
+)
+
type DebugLog struct {
- Enabled bool
- CacheType prebid_cache_client.PayloadType
- Data DebugData
- TTL int64
- CacheKey string
- CacheString string
- Regexp *regexp.Regexp
+ Enabled bool
+ CacheType prebid_cache_client.PayloadType
+ Data DebugData
+ TTL int64
+ CacheKey string
+ CacheString string
+ Regexp *regexp.Regexp
+ DebugOverride bool
+ //little optimization, it stores value of debugLog.Enabled || debugLog.DebugOverride
+ DebugEnabledOrOverridden bool
}
type DebugData struct {
@@ -47,6 +54,10 @@ func (d *DebugLog) BuildCacheString() {
d.CacheString = fmt.Sprintf("%s%s%s%s", xml.Header, d.Data.Request, d.Data.Headers, d.Data.Response)
}
+func IsDebugOverrideEnabled(debugHeader, configOverrideToken string) bool {
+ return configOverrideToken != "" && debugHeader == configOverrideToken
+}
+
func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout int, errors []error) error {
if len(d.Data.Response) == 0 && len(errors) == 0 {
d.Data.Response = "No response or errors created"
@@ -238,7 +249,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client,
}
}
- if len(toCache) > 0 && debugLog != nil && debugLog.Enabled {
+ if len(toCache) > 0 && debugLog != nil && debugLog.DebugEnabledOrOverridden {
debugLog.CacheKey = hbCacheID
debugLog.BuildCacheString()
if jsonBytes, err := json.Marshal(debugLog.CacheString); err == nil {
diff --git a/exchange/auction_test.go b/exchange/auction_test.go
index 1730309287c..04aef256a81 100644
--- a/exchange/auction_test.go
+++ b/exchange/auction_test.go
@@ -132,6 +132,56 @@ func TestCacheJSON(t *testing.T) {
}
}
+func TestIsDebugOverrideEnabled(t *testing.T) {
+ type inTest struct {
+ debugHeader string
+ configToken string
+ }
+ type aTest struct {
+ desc string
+ in inTest
+ result bool
+ }
+ testCases := []aTest{
+ {
+ desc: "test debug header is empty, config token is empty",
+ in: inTest{debugHeader: "", configToken: ""},
+ result: false,
+ },
+ {
+ desc: "test debug header is present, config token is empty",
+ in: inTest{debugHeader: "TestToken", configToken: ""},
+ result: false,
+ },
+ {
+ desc: "test debug header is empty, config token is present",
+ in: inTest{debugHeader: "", configToken: "TestToken"},
+ result: false,
+ },
+ {
+ desc: "test debug header is present, config token is present, not equal",
+ in: inTest{debugHeader: "TestToken123", configToken: "TestToken"},
+ result: false,
+ },
+ {
+ desc: "test debug header is present, config token is present, equal",
+ in: inTest{debugHeader: "TestToken", configToken: "TestToken"},
+ result: true,
+ },
+ {
+ desc: "test debug header is present, config token is present, not case equal",
+ in: inTest{debugHeader: "TestTokeN", configToken: "TestToken"},
+ result: false,
+ },
+ }
+
+ for _, test := range testCases {
+ result := IsDebugOverrideEnabled(test.in.debugHeader, test.in.configToken)
+ assert.Equal(t, test.result, result, test.desc)
+ }
+
+}
+
// LoadCacheSpec reads and parses a file as a test case. If something goes wrong, it returns an error.
func loadCacheSpec(filename string) (*cacheSpec, error) {
specData, err := ioutil.ReadFile(filename)
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 07d222c9602..8dcc9b5b856 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -49,7 +49,7 @@ type adaptedBidder interface {
//
// Any errors will be user-facing in the API.
// Error messages should help publishers understand what might account for "bad" bids.
- requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error)
+ requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error)
}
// pbsOrtbBid is a Bid returned by an adaptedBidder.
@@ -128,7 +128,7 @@ type bidderAdapterConfig struct {
DebugInfo config.DebugInfo
}
-func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
+func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo)
if len(reqData) == 0 {
@@ -139,6 +139,19 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
return nil, errs
}
+ if reqInfo.GlobalPrivacyControlHeader == "1" {
+ for i := 0; i < len(reqData); i++ {
+ if reqData[i].Headers != nil {
+ reqHeader := reqData[i].Headers.Clone()
+ reqHeader.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader)
+ reqData[i].Headers = reqHeader
+ } else {
+ reqData[i].Headers = http.Header{}
+ reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader)
+ }
+ }
+ }
+
// Make any HTTP requests in parallel.
// If the bidder only needs to make one, save some cycles by just using the current one.
responseChannel := make(chan *httpCallInfo, len(reqData))
@@ -165,19 +178,25 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
httpInfo := <-responseChannel
// If this is a test bid, capture debugging info from the requests.
// Write debug data to ext in case if:
+ // - headerDebugAllowed (debug override header specified correct) - it overrides all other debug restrictions
// - debugContextKey (url param) in true
// - account debug is allowed
// - bidder debug is allowed
- if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) {
- if accountDebugAllowed {
- if bidder.config.DebugInfo.Allow {
- seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
- } else {
- debugDisabledWarning := errortypes.Warning{
- WarningCode: errortypes.BidderLevelDebugDisabledWarningCode,
- Message: "debug turned off for bidder",
+ if headerDebugAllowed {
+ seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
+ } else {
+ debugInfo := ctx.Value(DebugContextKey)
+ if debugInfo != nil && debugInfo.(bool) {
+ if accountDebugAllowed {
+ if bidder.config.DebugInfo.Allow {
+ seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
+ } else {
+ debugDisabledWarning := errortypes.Warning{
+ WarningCode: errortypes.BidderLevelDebugDisabledWarningCode,
+ Message: "debug turned off for bidder",
+ }
+ errs = append(errs, &debugDisabledWarning)
}
- errs = append(errs, &debugDisabledWarning)
}
}
}
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index a3a0acbe5f0..87e1a8d8366 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -94,7 +94,7 @@ func TestSingleBidder(t *testing.T) {
bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo)
currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
+ seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
// Make sure the goodSingleBidder was called with the expected arguments.
if bidderImpl.httpResponse == nil {
@@ -167,7 +167,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
+ seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
expectedHttpCalls := []*openrtb_ext.ExtHttpCall{
{
@@ -183,6 +183,85 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCalls)
}
+func TestSetGPCHeader(t *testing.T) {
+ server := httptest.NewServer(mockHandler(200, "getBody", "responseJson"))
+ defer server.Close()
+
+ requestHeaders := http.Header{}
+ requestHeaders.Add("Content-Type", "application/json")
+
+ bidderImpl := &goodSingleBidder{
+ httpRequest: &adapters.RequestData{
+ Method: "POST",
+ Uri: server.URL,
+ Body: []byte("requestJson"),
+ Headers: requestHeaders,
+ },
+ bidResponse: &adapters.BidderResponse{
+ Bids: []*adapters.TypedBid{},
+ },
+ }
+
+ debugInfo := &config.DebugInfo{Allow: true}
+ ctx := context.Background()
+ ctx = context.WithValue(ctx, DebugContextKey, true)
+
+ bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+ currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+ seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false)
+
+ expectedHttpCall := []*openrtb_ext.ExtHttpCall{
+ {
+ Uri: server.URL,
+ RequestBody: "requestJson",
+ RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "Sec-Gpc": {"1"}},
+ ResponseBody: "responseJson",
+ Status: 200,
+ },
+ }
+
+ assert.Empty(t, errs)
+ assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCall)
+}
+
+func TestSetGPCHeaderNil(t *testing.T) {
+ server := httptest.NewServer(mockHandler(200, "getBody", "responseJson"))
+ defer server.Close()
+
+ bidderImpl := &goodSingleBidder{
+ httpRequest: &adapters.RequestData{
+ Method: "POST",
+ Uri: server.URL,
+ Body: []byte("requestJson"),
+ Headers: nil,
+ },
+ bidResponse: &adapters.BidderResponse{
+ Bids: []*adapters.TypedBid{},
+ },
+ }
+
+ debugInfo := &config.DebugInfo{Allow: true}
+ ctx := context.Background()
+ ctx = context.WithValue(ctx, DebugContextKey, true)
+
+ bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+ currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+ seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false)
+
+ expectedHttpCall := []*openrtb_ext.ExtHttpCall{
+ {
+ Uri: server.URL,
+ RequestBody: "requestJson",
+ RequestHeaders: map[string][]string{"Sec-Gpc": {"1"}},
+ ResponseBody: "responseJson",
+ Status: 200,
+ },
+ }
+
+ assert.Empty(t, errs)
+ assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCall)
+}
+
// TestMultiBidder makes sure all the requests get sent, and the responses processed.
// Because this is done in parallel, it should be run under the race detector.
func TestMultiBidder(t *testing.T) {
@@ -225,7 +304,7 @@ func TestMultiBidder(t *testing.T) {
}
bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
+ seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true)
if seatBid == nil {
t.Fatalf("SeatBid should exist, because bids exist.")
@@ -485,7 +564,7 @@ func TestMultiCurrencies(t *testing.T) {
{currency: "USD", price: 1.3 * 1.3050530256},
},
expectedBadCurrencyErrors: []error{
- errors.New("Currency conversion rate not found: 'JPY' => 'USD'"),
+ currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"},
},
description: "Case 6 - Bidder respond with a mix of currencies and one unknown on all HTTP responses",
},
@@ -508,9 +587,9 @@ func TestMultiCurrencies(t *testing.T) {
},
expectedBids: []bid{},
expectedBadCurrencyErrors: []error{
- errors.New("Currency conversion rate not found: 'JPY' => 'USD'"),
- errors.New("Currency conversion rate not found: 'BZD' => 'USD'"),
- errors.New("Currency conversion rate not found: 'DKK' => 'USD'"),
+ currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"},
+ currency.ConversionNotFoundError{FromCur: "BZD", ToCur: "USD"},
+ currency.ConversionNotFoundError{FromCur: "DKK", ToCur: "USD"},
},
description: "Case 7 - Bidder respond with currencies not having any rate on all HTTP responses",
},
@@ -602,6 +681,7 @@ func TestMultiCurrencies(t *testing.T) {
currencyConverter.Rates(),
&adapters.ExtraRequestInfo{},
true,
+ true,
)
// Verify:
@@ -640,9 +720,9 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
bidCurrency: []string{"EUR", "EUR", "EUR"},
expectedBidsCount: 0,
expectedBadCurrencyErrors: []error{
- fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"),
- fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"),
- fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"),
+ currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"},
+ currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"},
+ currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"},
},
description: "Case 2 - Bidder respond with the same currency (not default one) on all HTTP responses",
},
@@ -674,7 +754,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
bidCurrency: []string{"EUR", "", "USD"},
expectedBidsCount: 2,
expectedBadCurrencyErrors: []error{
- fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"),
+ currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"},
},
description: "Case 7 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses",
},
@@ -682,7 +762,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
bidCurrency: []string{"GBP", "", "USD"},
expectedBidsCount: 2,
expectedBadCurrencyErrors: []error{
- fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'GBP' => 'USD'"),
+ currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"},
},
description: "Case 8 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses",
},
@@ -690,7 +770,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
bidCurrency: []string{"GBP", "", ""},
expectedBidsCount: 2,
expectedBadCurrencyErrors: []error{
- fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'GBP' => 'USD'"),
+ currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"},
},
description: "Case 9 - Bidder responds with a mix of not set and empty currencies (default currency) in HTTP responses",
},
@@ -747,6 +827,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
currencyConverter.Rates(),
&adapters.ExtraRequestInfo{},
true,
+ true,
)
// Verify:
@@ -920,6 +1001,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
currencyConverter.Rates(),
&adapters.ExtraRequestInfo{},
true,
+ false,
)
// Verify:
@@ -1224,6 +1306,7 @@ func TestMobileNativeTypes(t *testing.T) {
currencyConverter.Rates(),
&adapters.ExtraRequestInfo{},
true,
+ true,
)
var actualValue string
@@ -1237,7 +1320,7 @@ func TestMobileNativeTypes(t *testing.T) {
func TestErrorReporting(t *testing.T) {
bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
+ bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false)
if bids != nil {
t.Errorf("There should be no seatbid if no http requests are returned.")
}
@@ -1458,7 +1541,7 @@ func TestCallRecordAdapterConnections(t *testing.T) {
// Run requestBid using an http.Client with a mock handler
bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil)
currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
+ _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true)
// Assert no errors
assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go
index 3d2eb0b8e42..d58c9ebba50 100644
--- a/exchange/bidder_validate_bids.go
+++ b/exchange/bidder_validate_bids.go
@@ -28,8 +28,8 @@ type validatedBidder struct {
bidder adaptedBidder
}
-func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
- seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed)
+func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
+ seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed)
if validationErrors := removeInvalidBids(request, seatBid); len(validationErrors) > 0 {
errs = append(errs, validationErrors...)
}
@@ -110,8 +110,11 @@ func validateBid(bid *pbsOrtbBid) (bool, error) {
if bid.bid.ImpID == "" {
return false, fmt.Errorf("Bid \"%s\" missing required field 'impid'", bid.bid.ID)
}
- if bid.bid.Price <= 0.0 {
- return false, fmt.Errorf("Bid \"%s\" does not contain a positive 'price'", bid.bid.ID)
+ if bid.bid.Price < 0.0 {
+ return false, fmt.Errorf("Bid \"%s\" does not contain a positive (or zero if there is a deal) 'price'", bid.bid.ID)
+ }
+ if bid.bid.Price == 0.0 && bid.bid.DealID == "" {
+ return false, fmt.Errorf("Bid \"%s\" does not contain positive 'price' which is required since there is no deal set for this bid", bid.bid.ID)
}
if bid.bid.CrID == "" {
return false, fmt.Errorf("Bid \"%s\" missing creative ID", bid.bid.ID)
diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go
index 3bb43559856..37c7bbec1eb 100644
--- a/exchange/bidder_validate_bids_test.go
+++ b/exchange/bidder_validate_bids_test.go
@@ -39,11 +39,20 @@ func TestAllValidBids(t *testing.T) {
CrID: "789",
},
},
+ {
+ bid: &openrtb2.Bid{
+ ID: "zeroPriceBid",
+ ImpID: "444",
+ Price: 0.00,
+ CrID: "555",
+ DealID: "777",
+ },
+ },
},
},
})
- seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true)
- assert.Len(t, seatBid.bids, 3)
+ seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
+ assert.Len(t, seatBid.bids, 4)
assert.Len(t, errs, 0)
}
@@ -79,13 +88,30 @@ func TestAllBadBids(t *testing.T) {
CrID: "blah",
},
},
+ {
+ bid: &openrtb2.Bid{
+ ID: "zeroPriceBidNoDeal",
+ ImpID: "444",
+ Price: 0.00,
+ CrID: "555",
+ DealID: "",
+ },
+ },
+ {
+ bid: &openrtb2.Bid{
+ ID: "negativePrice",
+ ImpID: "999",
+ Price: -0.10,
+ CrID: "888",
+ },
+ },
{},
},
},
})
- seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true)
+ seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
assert.Len(t, seatBid.bids, 0)
- assert.Len(t, errs, 5)
+ assert.Len(t, errs, 7)
}
func TestMixedBids(t *testing.T) {
@@ -122,13 +148,39 @@ func TestMixedBids(t *testing.T) {
CrID: "blah",
},
},
+ {
+ bid: &openrtb2.Bid{
+ ID: "zeroPriceBid",
+ ImpID: "444",
+ Price: 0.00,
+ CrID: "555",
+ DealID: "777",
+ },
+ },
+ {
+ bid: &openrtb2.Bid{
+ ID: "zeroPriceBidNoDeal",
+ ImpID: "444",
+ Price: 0.00,
+ CrID: "555",
+ DealID: "",
+ },
+ },
+ {
+ bid: &openrtb2.Bid{
+ ID: "negativePrice",
+ ImpID: "999",
+ Price: -0.10,
+ CrID: "888",
+ },
+ },
{},
},
},
})
- seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true)
- assert.Len(t, seatBid.bids, 2)
- assert.Len(t, errs, 3)
+ seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
+ assert.Len(t, seatBid.bids, 3)
+ assert.Len(t, errs, 5)
}
func TestCurrencyBids(t *testing.T) {
@@ -246,7 +298,7 @@ func TestCurrencyBids(t *testing.T) {
Cur: tc.brqCur,
}
- seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true)
+ seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false)
assert.Len(t, seatBid.bids, expectedValidBids)
assert.Len(t, errs, expectedErrs)
}
@@ -257,6 +309,6 @@ type mockAdaptedBidder struct {
errorResponse []error
}
-func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
+func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
return b.bidResponse, b.errorResponse
}
diff --git a/exchange/cachetest/debuglog_enabled.json b/exchange/cachetest/debuglog_enabled.json
index e6c85c57055..faba3ed690d 100644
--- a/exchange/cachetest/debuglog_enabled.json
+++ b/exchange/cachetest/debuglog_enabled.json
@@ -1,6 +1,8 @@
{
"debugLog": {
"Enabled": true,
+ "DebugEnabledOrOverridden": true,
+ "DebugOverride": false,
"CacheType": "xml",
"TTL": 3600,
"Data": {
diff --git a/exchange/events.go b/exchange/events.go
index 9742e50e424..06f26b7e333 100644
--- a/exchange/events.go
+++ b/exchange/events.go
@@ -2,10 +2,10 @@ package exchange
import (
"encoding/json"
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
"time"
- jsonpatch "github.com/evanphx/json-patch"
- "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/evanphx/json-patch"
"github.com/prebid/prebid-server/analytics"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/endpoints/events"
diff --git a/exchange/exchange.go b/exchange/exchange.go
index b28c60e4fbe..fa8b51901ff 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -52,19 +52,19 @@ type IdFetcher interface {
}
type exchange struct {
- adapterMap map[openrtb_ext.BidderName]adaptedBidder
- bidderInfo config.BidderInfos
- me metrics.MetricsEngine
- cache prebid_cache_client.Client
- cacheTime time.Duration
- gDPR gdpr.Permissions
- currencyConverter *currency.RateConverter
- externalURL string
- UsersyncIfAmbiguous bool
- privacyConfig config.Privacy
- categoriesFetcher stored_requests.CategoryFetcher
- bidIDGenerator BidIDGenerator
- trakerURL string
+ adapterMap map[openrtb_ext.BidderName]adaptedBidder
+ bidderInfo config.BidderInfos
+ me metrics.MetricsEngine
+ cache prebid_cache_client.Client
+ cacheTime time.Duration
+ gDPR gdpr.Permissions
+ currencyConverter *currency.RateConverter
+ externalURL string
+ gdprDefaultValue gdpr.Signal
+ privacyConfig config.Privacy
+ categoriesFetcher stored_requests.CategoryFetcher
+ bidIDGenerator BidIDGenerator
+ trakerURL string
}
// Container to pass out response ext data from the GetAllBids goroutines back into the main thread
@@ -101,18 +101,33 @@ func (big *bidIDGenerator) New() (string, error) {
return rawUuid.String(), err
}
+type deduplicateChanceGenerator interface {
+ Generate() bool
+}
+
+type randomDeduplicateBidBooleanGenerator struct{}
+
+func (randomDeduplicateBidBooleanGenerator) Generate() bool {
+ return rand.Intn(100) < 50
+}
+
func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange {
+ gdprDefaultValue := gdpr.SignalYes
+ if cfg.GDPR.DefaultValue == "0" {
+ gdprDefaultValue = gdpr.SignalNo
+ }
+
return &exchange{
- adapterMap: adapters,
- bidderInfo: infos,
- cache: cache,
- cacheTime: time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond,
- categoriesFetcher: categoriesFetcher,
- currencyConverter: currencyConverter,
- externalURL: cfg.ExternalURL,
- gDPR: gDPR,
- me: metricsEngine,
- UsersyncIfAmbiguous: cfg.GDPR.UsersyncIfAmbiguous,
+ adapterMap: adapters,
+ bidderInfo: infos,
+ cache: cache,
+ cacheTime: time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond,
+ categoriesFetcher: categoriesFetcher,
+ currencyConverter: currencyConverter,
+ externalURL: cfg.ExternalURL,
+ gDPR: gDPR,
+ me: metricsEngine,
+ gdprDefaultValue: gdprDefaultValue,
privacyConfig: config.Privacy{
CCPA: cfg.CCPA,
GDPR: cfg.GDPR,
@@ -126,12 +141,13 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid
// AuctionRequest holds the bid request for the auction
// and all other information needed to process that request
type AuctionRequest struct {
- BidRequest *openrtb2.BidRequest
- Account config.Account
- UserSyncs IdFetcher
- RequestType metrics.RequestType
- StartTime time.Time
- Warnings []error
+ BidRequest *openrtb2.BidRequest
+ Account config.Account
+ UserSyncs IdFetcher
+ RequestType metrics.RequestType
+ StartTime time.Time
+ Warnings []error
+ GlobalPrivacyControlHeader string
// LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests
// in HoldAuction until we get to factoring it away. Do not use for anything new.
@@ -161,13 +177,13 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
}
if debugLog == nil {
- debugLog = &DebugLog{Enabled: false}
+ debugLog = &DebugLog{Enabled: false, DebugEnabledOrOverridden: false}
}
requestDebugInfo := getDebugInfo(r.BidRequest, requestExt)
- debugInfo := requestDebugInfo && r.Account.DebugAllow
- debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow
+ debugInfo := debugLog.DebugEnabledOrOverridden || (requestDebugInfo && r.Account.DebugAllow)
+ debugLog.Enabled = debugLog.DebugEnabledOrOverridden || r.Account.DebugAllow
if debugInfo {
ctx = e.makeDebugContext(ctx, debugInfo)
@@ -178,10 +194,10 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
recordImpMetrics(r.BidRequest, e.me)
// Make our best guess if GDPR applies
- usersyncIfAmbiguous := e.parseUsersyncIfAmbiguous(r.BidRequest)
+ gdprDefaultValue := e.parseGDPRDefaultValue(r.BidRequest)
// Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder
- bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, usersyncIfAmbiguous, e.privacyConfig, &r.Account)
+ bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, e.me, gdprDefaultValue, e.privacyConfig, &r.Account)
e.me.RecordRequestPrivacy(privacyLabels)
@@ -194,9 +210,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
defer cancel()
// Get currency rates conversions for the auction
- conversions := e.currencyConverter.Rates()
+ conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions)
- adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow)
+ adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride)
var auc *auction
var cacheErrs []error
@@ -213,7 +229,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
//If includebrandcategory is present in ext then CE feature is on.
if requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil {
var rejections []string
- bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r.BidRequest, requestExt, adapterBids, e.categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r.BidRequest, requestExt, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
if err != nil {
return nil, fmt.Errorf("Error in category mapping : %s", err.Error())
}
@@ -247,7 +263,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
}
bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs)
- if debugLog.Enabled {
+ if debugLog.DebugEnabledOrOverridden {
if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
debugLog.Data.Response = string(bidRespExtBytes)
} else {
@@ -268,7 +284,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
} else {
bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs)
- if debugLog.Enabled {
+ if debugLog.DebugEnabledOrOverridden {
if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
debugLog.Data.Response = string(bidRespExtBytes)
@@ -279,7 +295,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
}
}
- if !r.Account.DebugAllow && requestDebugInfo {
+ if !r.Account.DebugAllow && requestDebugInfo && !debugLog.DebugOverride {
accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{
Code: errortypes.AccountLevelDebugDisabledWarningCode,
Message: "debug turned off for account",
@@ -299,8 +315,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs)
}
-func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) bool {
- usersyncIfAmbiguous := e.UsersyncIfAmbiguous
+func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) gdpr.Signal {
+ gdprDefaultValue := e.gdprDefaultValue
var geo *openrtb2.Geo = nil
if bidRequest.User != nil && bidRequest.User.Geo != nil {
@@ -312,14 +328,14 @@ func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) boo
// If we have a country set, and it is on the list, we assume GDPR applies if not set on the request.
// Otherwise we assume it does not apply as long as it appears "valid" (is 3 characters long).
if _, found := e.privacyConfig.GDPR.EEACountriesMap[strings.ToUpper(geo.Country)]; found {
- usersyncIfAmbiguous = false
+ gdprDefaultValue = gdpr.SignalYes
} else if len(geo.Country) == 3 {
// The country field is formatted properly as a three character country code
- usersyncIfAmbiguous = true
+ gdprDefaultValue = gdpr.SignalNo
}
}
- return usersyncIfAmbiguous
+ return gdprDefaultValue
}
func recordImpMetrics(bidRequest *openrtb2.BidRequest, metricsEngine metrics.MetricsEngine) {
@@ -411,7 +427,9 @@ func (e *exchange) getAllBids(
bidderRequests []BidderRequest,
bidAdjustments map[string]float64,
conversions currency.Conversions,
- accountDebugAllowed bool) (
+ accountDebugAllowed bool,
+ globalPrivacyControlHeader string,
+ headerDebugAllowed bool) (
map[openrtb_ext.BidderName]*pbsOrtbSeatBid,
map[openrtb_ext.BidderName]*seatResponseExtra, bool) {
// Set up pointers to the bid results
@@ -441,9 +459,10 @@ func (e *exchange) getAllBids(
if givenAdjustment, ok := bidAdjustments[string(bidderRequest.BidderName)]; ok {
adjustmentFactor = givenAdjustment
}
- var reqInfo adapters.ExtraRequestInfo
+ reqInfo := adapters.NewExtraRequestInfo(conversions)
reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType
- bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed)
+ reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader
+ bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed)
// Add in time reporting
elapsed := time.Since(start)
@@ -499,7 +518,6 @@ func (e *exchange) getAllBids(
bidsFound = true
bidIDsCollision = recordAdaptorDuplicateBidIDs(e.me, adapterBids)
}
-
}
if bidIDsCollision {
// record this request count this request if bid collision is detected
@@ -644,7 +662,7 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e
return buffer.Bytes(), err
}
-func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) {
+func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) {
res := make(map[string]string)
type bidDedupe struct {
@@ -787,7 +805,7 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest,
currBidPrice = 0
}
if dupeBidPrice == currBidPrice {
- if rand.Intn(100) < 50 {
+ if booleanGenerator.Generate() {
dupeBidPrice = -1
} else {
currBidPrice = -1
@@ -802,11 +820,16 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest,
} else {
// An older bid from a different seatBid we've already finished with
oldSeatBid := (seatBids)[dupe.bidderName]
+ rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated")
if len(oldSeatBid.bids) == 1 {
seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName)
- rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated")
} else {
- oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...)
+ // This is a very rare, but still possible case where bid needs to be removed from already processed bidder
+ // This happens when current processing bidder has a bid that has same deduplication key as a bid from already processed bidder
+ // and already processed bid was selected to be removed
+ // See example of input data in unit test `TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice`
+ // Need to remove bid by name, not index in this case
+ removeBidById(oldSeatBid, dupe.bidID)
}
}
delete(res, dupe.bidID)
@@ -817,9 +840,9 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest,
continue
}
}
- dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb}
}
res[bidID] = categoryDuration
+ dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb}
}
if len(bidsToRemove) > 0 {
@@ -845,6 +868,24 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest,
return res, seatBids, rejections, nil
}
+func removeBidById(seatBid *pbsOrtbSeatBid, bidID string) {
+ //Find index of bid to remove
+ dupeBidIndex := -1
+ for i, bid := range seatBid.bids {
+ if bid.bid.ID == bidID {
+ dupeBidIndex = i
+ break
+ }
+ }
+ if dupeBidIndex != -1 {
+ if dupeBidIndex < len(seatBid.bids)-1 {
+ seatBid.bids = append(seatBid.bids[:dupeBidIndex], seatBid.bids[dupeBidIndex+1:]...)
+ } else if dupeBidIndex == len(seatBid.bids)-1 {
+ seatBid.bids = seatBid.bids[:len(seatBid.bids)-1]
+ }
+ }
+}
+
func updateRejections(rejections []string, bidID string, reason string) []string {
message := fmt.Sprintf("bid rejected [bid ID: %s] reason: %s", bidID, reason)
return append(rejections, message)
@@ -986,6 +1027,34 @@ func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo
return
}
+func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestCurrency) currency.Conversions {
+ if requestRates == nil {
+ // No bidRequest.ext.currency field was found, use PBS rates as usual
+ return e.currencyConverter.Rates()
+ }
+
+ // If bidRequest.ext.currency.usepbsrates is nil, we understand its value as true. It will be false
+ // only if it's explicitly set to false
+ usePbsRates := requestRates.UsePBSRates == nil || *requestRates.UsePBSRates
+
+ if !usePbsRates {
+ // At this point, we can safely assume the ConversionRates map is not empty because
+ // validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) would have
+ // thrown an error under such conditions.
+ return currency.NewRates(time.Time{}, requestRates.ConversionRates)
+ }
+
+ // Both PBS and custom rates can be used, check if ConversionRates is not empty
+ if len(requestRates.ConversionRates) == 0 {
+ // Custom rates map is empty, use PBS rates only
+ return e.currencyConverter.Rates()
+ }
+
+ // Return an AggregateConversions object that includes both custom and PBS currency rates but will
+ // prioritize custom rates over PBS rates whenever a currency rate is found in both
+ return currency.NewAggregateConversions(currency.NewRates(time.Time{}, requestRates.ConversionRates), e.currencyConverter.Rates())
+}
+
func findCacheID(bid *pbsOrtbBid, auction *auction) (string, bool) {
if bid != nil && bid.bid != nil && auction != nil {
if id, found := auction.cacheIds[bid.bid]; found {
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index fbce03f6810..6f49b754bb9 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -32,6 +32,7 @@ import (
"github.com/buger/jsonparser"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
"github.com/yudai/gojsondiff"
"github.com/yudai/gojsondiff/formatter"
)
@@ -87,8 +88,8 @@ func TestNewExchange(t *testing.T) {
// 4) Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... )
// 5) Assert we have no '&' characters in the response that exchange.buildBidResponse returns
func TestCharacterEscape(t *testing.T) {
- /* 1) Adapter with a '& char in its endpoint property */
- /* https://github.com/prebid/prebid-server/issues/465 */
+ // 1) Adapter with a '& char in its endpoint property
+ // https://github.com/prebid/prebid-server/issues/465
cfg := &config.Configuration{
Adapters: make(map[string]config.Adapter, 1),
}
@@ -96,7 +97,7 @@ func TestCharacterEscape(t *testing.T) {
Endpoint: "http://ib.adnxs.com/openrtb2?query1&query2", //Note the '&' character in there
}
- /* 2) Init new exchange with said configuration */
+ // 2) Init new exchange with said configuration
//Other parameters also needed to create exchange
handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
@@ -115,7 +116,7 @@ func TestCharacterEscape(t *testing.T) {
currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
e := NewExchange(adapters, nil, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange)
- /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */
+ // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
//liveAdapters []openrtb_ext.BidderName,
liveAdapters := make([]openrtb_ext.BidderName, 1)
liveAdapters[0] = "appnexus"
@@ -148,10 +149,10 @@ func TestCharacterEscape(t *testing.T) {
var errList []error
- /* 4) Build bid response */
+ // 4) Build bid response
bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, errList)
- /* 5) Assert we have no errors and one '&' character as we are supposed to */
+ // 5) Assert we have no errors and one '&' character as we are supposed to
if err != nil {
t.Errorf("exchange.buildBidResponse returned unexpected error: %v", err)
}
@@ -177,8 +178,9 @@ func TestDebugBehaviour(t *testing.T) {
}
type debugData struct {
- bidderLevelDebugAllowed bool
- accountLevelDebugAllowed bool
+ bidderLevelDebugAllowed bool
+ accountLevelDebugAllowed bool
+ headerOverrideDebugAllowed bool
}
type aTest struct {
@@ -193,57 +195,78 @@ func TestDebugBehaviour(t *testing.T) {
desc: "test flag equals zero, ext debug flag false, no debug info expected",
in: inTest{test: 0, debug: false},
out: outTest{debugInfoIncluded: false},
- debugData: debugData{true, true},
+ debugData: debugData{true, true, false},
generateWarnings: false,
},
{
desc: "test flag equals zero, ext debug flag true, debug info expected",
in: inTest{test: 0, debug: true},
out: outTest{debugInfoIncluded: true},
- debugData: debugData{true, true},
+ debugData: debugData{true, true, false},
generateWarnings: false,
},
{
desc: "test flag equals 1, ext debug flag false, debug info expected",
in: inTest{test: 1, debug: false},
out: outTest{debugInfoIncluded: true},
- debugData: debugData{true, true},
+ debugData: debugData{true, true, false},
generateWarnings: false,
},
{
desc: "test flag equals 1, ext debug flag true, debug info expected",
in: inTest{test: 1, debug: true},
out: outTest{debugInfoIncluded: true},
- debugData: debugData{true, true},
+ debugData: debugData{true, true, false},
generateWarnings: false,
},
{
desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected",
in: inTest{test: 2, debug: false},
out: outTest{debugInfoIncluded: false},
- debugData: debugData{true, true},
+ debugData: debugData{true, true, false},
generateWarnings: false,
},
{
desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected",
in: inTest{test: -1, debug: true},
out: outTest{debugInfoIncluded: true},
- debugData: debugData{true, true},
+ debugData: debugData{true, true, false},
generateWarnings: true,
},
{
desc: "test account level debug disabled",
in: inTest{test: -1, debug: true},
out: outTest{debugInfoIncluded: false},
- debugData: debugData{true, false},
+ debugData: debugData{true, false, false},
generateWarnings: true,
},
{
- desc: "test bidder level debug disabled",
+ desc: "test header override enabled when all other debug options are disabled",
+ in: inTest{test: -1, debug: false},
+ out: outTest{debugInfoIncluded: true},
+ debugData: debugData{false, false, true},
+ generateWarnings: false,
+ },
+ {
+ desc: "test header override and url debug options are enabled when all other debug options are disabled",
in: inTest{test: -1, debug: true},
- out: outTest{debugInfoIncluded: false},
- debugData: debugData{false, true},
- generateWarnings: true,
+ out: outTest{debugInfoIncluded: true},
+ debugData: debugData{false, false, true},
+ generateWarnings: false,
+ },
+ {
+ desc: "test header override and url and bidder debug options are enabled when account debug option is disabled",
+ in: inTest{test: -1, debug: true},
+ out: outTest{debugInfoIncluded: true},
+ debugData: debugData{true, false, true},
+ generateWarnings: false,
+ },
+ {
+ desc: "test all debug options are enabled",
+ in: inTest{test: -1, debug: true},
+ out: outTest{debugInfoIncluded: true},
+ debugData: debugData{true, true, true},
+ generateWarnings: false,
},
}
@@ -323,9 +346,12 @@ func TestDebugBehaviour(t *testing.T) {
WarningCode: errortypes.InvalidPrivacyConsentWarningCode})
auctionRequest.Warnings = errL
}
-
+ debugLog := &DebugLog{}
+ if test.debugData.headerOverrideDebugAllowed {
+ debugLog = &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true}
+ }
// Run test
- outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil)
+ outBidResponse, err := e.HoldAuction(ctx, auctionRequest, debugLog)
// Assert no HoldAuction error
assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err)
@@ -339,6 +365,11 @@ func TestDebugBehaviour(t *testing.T) {
assert.NotEmpty(t, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp should not be empty when AuctionRequest.StartTime is set")
assert.Equal(t, auctionRequest.StartTime.UnixNano()/1e+6, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp has incorrect value")
+ if test.debugData.headerOverrideDebugAllowed {
+ assert.Empty(t, actualExt.Warnings, "warnings should be empty")
+ assert.Empty(t, actualExt.Errors, "errors should be empty")
+ }
+
if test.out.debugInfoIncluded {
assert.NotNilf(t, actualExt, "%s. ext.debug field is expected to be included in this outBidResponse.Ext and not be nil. outBidResponse.Ext.Debug = %v \n", test.desc, actualExt.Debug)
@@ -358,13 +389,13 @@ func TestDebugBehaviour(t *testing.T) {
assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty")
}
- if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed {
+ if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed {
assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning")
assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present")
assert.Equal(t, "debug turned off for account", actualExt.Warnings["general"][0].Message, "account debug disabled message should be present")
}
- if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed {
+ if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed {
if test.generateWarnings {
assert.Len(t, actualExt.Warnings, 2, "warnings should have one warning")
} else {
@@ -508,6 +539,432 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
}
+func TestOverrideWithCustomCurrency(t *testing.T) {
+
+ mockCurrencyClient := &fakeCurrencyRatesHttpClient{
+ responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`,
+ }
+ mockCurrencyConverter := currency.NewRateConverter(
+ mockCurrencyClient,
+ "currency.fake.com",
+ 24*time.Hour,
+ )
+
+ type testIn struct {
+ customCurrencyRates json.RawMessage
+ bidRequestCurrency string
+ }
+ type testResults struct {
+ numBids int
+ bidRespPrice float64
+ bidRespCurrency string
+ }
+
+ testCases := []struct {
+ desc string
+ in testIn
+ expected testResults
+ }{
+ {
+ desc: "Blank currency field in ext. bidRequest comes with a valid currency but conversion rate was not found in PBS. Return no bids",
+ in: testIn{
+ customCurrencyRates: json.RawMessage(`{ "prebid": { "currency": {} } } `),
+ bidRequestCurrency: "GBP",
+ },
+ expected: testResults{},
+ },
+ {
+ desc: "valid request.ext.prebid.currency, expect custom rates to override those of the currency rate server",
+ in: testIn{
+ customCurrencyRates: json.RawMessage(`{
+ "prebid": {
+ "currency": {
+ "rates": {
+ "USD": {
+ "MXN": 20.00,
+ "EUR": 10.95
+ }
+ }
+ }
+ }
+ }`),
+ bidRequestCurrency: "MXN",
+ },
+ expected: testResults{
+ numBids: 1,
+ bidRespPrice: 20.00,
+ bidRespCurrency: "MXN",
+ },
+ },
+ }
+
+ // Init mock currency conversion service
+ mockCurrencyConverter.Run()
+
+ // Init an exchange to run an auction from
+ noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
+ mockAppnexusBidService := httptest.NewServer(http.HandlerFunc(noBidServer))
+ defer mockAppnexusBidService.Close()
+
+ categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
+ if error != nil {
+ t.Errorf("Failed to create a category Fetcher: %v", error)
+ }
+
+ oneDollarBidBidder := &goodSingleBidder{
+ httpRequest: &adapters.RequestData{
+ Method: "POST",
+ Uri: mockAppnexusBidService.URL,
+ Body: []byte("{\"key\":\"val\"}"),
+ Headers: http.Header{},
+ },
+ }
+
+ e := new(exchange)
+ e.cache = &wellBehavedCache{}
+ e.me = &metricsConf.DummyMetricsEngine{}
+ e.gDPR = gdpr.AlwaysAllow{}
+ e.currencyConverter = mockCurrencyConverter
+ e.categoriesFetcher = categoriesFetcher
+ e.bidIDGenerator = &mockBidIDGenerator{false, false}
+
+ // Define mock incoming bid requeset
+ mockBidRequest := &openrtb2.BidRequest{
+ ID: "some-request-id",
+ Imp: []openrtb2.Imp{{
+ ID: "some-impression-id",
+ Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
+ Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`),
+ }},
+ Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
+ }
+
+ // Run tests
+ for _, test := range testCases {
+
+ oneDollarBidBidder.bidResponse = &adapters.BidderResponse{
+ Bids: []*adapters.TypedBid{
+ {
+ Bid: &openrtb2.Bid{Price: 1.00},
+ },
+ },
+ Currency: "USD",
+ }
+
+ e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
+ openrtb_ext.BidderAppnexus: adaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+ }
+
+ // Set custom rates in extension
+ mockBidRequest.Ext = test.in.customCurrencyRates
+
+ // Set bidRequest currency list
+ mockBidRequest.Cur = []string{test.in.bidRequestCurrency}
+
+ auctionRequest := AuctionRequest{
+ BidRequest: mockBidRequest,
+ Account: config.Account{},
+ UserSyncs: &emptyUsersync{},
+ }
+
+ // Run test
+ outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
+
+ // Assertions
+ assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err)
+
+ if test.expected.numBids > 0 {
+ // Assert out currency
+ assert.Equal(t, test.expected.bidRespCurrency, outBidResponse.Cur, "Bid response currency is wrong: %s \n", test.desc)
+
+ // Assert returned bid
+ if !assert.NotNil(t, outBidResponse, "outBidResponse is nil: %s \n", test.desc) {
+ return
+ }
+ if !assert.NotEmpty(t, outBidResponse.SeatBid, "outBidResponse.SeatBid is empty: %s", test.desc) {
+ return
+ }
+ if !assert.NotEmpty(t, outBidResponse.SeatBid[0].Bid, "outBidResponse.SeatBid[0].Bid is empty: %s", test.desc) {
+ return
+ }
+
+ // Assert returned bid price matches the currency conversion
+ assert.Equal(t, test.expected.bidRespPrice, outBidResponse.SeatBid[0].Bid[0].Price, "Bid response seatBid price is wrong: %s", test.desc)
+ } else {
+ assert.Len(t, outBidResponse.SeatBid, 0, "outBidResponse.SeatBid should be empty: %s", test.desc)
+ }
+ }
+}
+
+func TestAdapterCurrency(t *testing.T) {
+ fakeCurrencyClient := &fakeCurrencyRatesHttpClient{
+ responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`,
+ }
+ currencyConverter := currency.NewRateConverter(
+ fakeCurrencyClient,
+ "currency.fake.com",
+ 24*time.Hour,
+ )
+ currencyConverter.Run()
+
+ // Initialize Mock Bidder
+ // - Response purposefully causes PBS-Core to stop processing the request, since this test is only
+ // interested in the call to MakeRequests and nothing after.
+ mockBidder := &mockBidder{}
+ mockBidder.On("MakeRequests", mock.Anything, mock.Anything).Return([]*adapters.RequestData(nil), []error(nil))
+
+ // Initialize Real Exchange
+ e := exchange{
+ cache: &wellBehavedCache{},
+ me: &metricsConf.DummyMetricsEngine{},
+ gDPR: gdpr.AlwaysAllow{},
+ currencyConverter: currencyConverter,
+ categoriesFetcher: nilCategoryFetcher{},
+ bidIDGenerator: &mockBidIDGenerator{false, false},
+ adapterMap: map[openrtb_ext.BidderName]adaptedBidder{
+ openrtb_ext.BidderName("foo"): adaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderName("foo"), nil),
+ },
+ }
+
+ // Define Bid Request
+ request := &openrtb2.BidRequest{
+ ID: "some-request-id",
+ Imp: []openrtb2.Imp{{
+ ID: "some-impression-id",
+ Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
+ Ext: json.RawMessage(`{"foo": {"placementId": 1}}`),
+ }},
+ Site: &openrtb2.Site{
+ Page: "prebid.org",
+ Ext: json.RawMessage(`{"amp":0}`),
+ },
+ Cur: []string{"USD"},
+ Ext: json.RawMessage(`{"prebid": {"currency": {"rates": {"USD": {"MXN": 20.00}}}}}`),
+ }
+
+ // Run Auction
+ auctionRequest := AuctionRequest{
+ BidRequest: request,
+ Account: config.Account{},
+ UserSyncs: &emptyUsersync{},
+ }
+ response, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
+ assert.NoError(t, err)
+ assert.Equal(t, "some-request-id", response.ID, "Response ID")
+ assert.Empty(t, response.SeatBid, "Response Bids")
+ assert.Contains(t, string(response.Ext), `"errors":{"foo":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext")
+
+ // Test Currency Converter Properly Passed To Adapter
+ if assert.NotNil(t, mockBidder.lastExtraRequestInfo, "Currency Conversion Argument") {
+ converted, err := mockBidder.lastExtraRequestInfo.ConvertCurrency(2.0, "USD", "MXN")
+ assert.NoError(t, err, "Currency Conversion Error")
+ assert.Equal(t, 40.0, converted, "Currency Conversion Response")
+ }
+}
+
+func TestGetAuctionCurrencyRates(t *testing.T) {
+
+ pbsRates := map[string]map[string]float64{
+ "MXN": {
+ "USD": 20.13,
+ "EUR": 27.82,
+ "JPY": 5.09, // "MXN" to "JPY" rate not found in customRates
+ },
+ }
+
+ customRates := map[string]map[string]float64{
+ "MXN": {
+ "USD": 25.00, // different rate than in pbsRates
+ "EUR": 27.82, // same as in pbsRates
+ "GBP": 31.12, // not found in pbsRates at all
+ },
+ }
+
+ expectedRateEngineRates := map[string]map[string]float64{
+ "MXN": {
+ "USD": 25.00, // rates engine will prioritize the value found in custom rates
+ "EUR": 27.82, // same value in both the engine reads the custom entry first
+ "JPY": 5.09, // the engine will find it in the pbsRates conversions
+ "GBP": 31.12, // the engine will find it in the custom conversions
+ },
+ }
+
+ boolTrue := true
+ boolFalse := false
+
+ type testInput struct {
+ pbsRates map[string]map[string]float64
+ bidExtCurrency *openrtb_ext.ExtRequestCurrency
+ }
+ type testOutput struct {
+ constantRates bool
+ resultingRates map[string]map[string]float64
+ }
+ testCases := []struct {
+ desc string
+ given testInput
+ expected testOutput
+ }{
+ {
+ "valid pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates",
+ testInput{
+ pbsRates: pbsRates,
+ bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: customRates,
+ UsePBSRates: &boolFalse,
+ },
+ },
+ testOutput{
+ resultingRates: customRates,
+ },
+ },
+ {
+ "valid pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates are a mix but customRates gets priority",
+ testInput{
+ pbsRates: pbsRates,
+ bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: customRates,
+ UsePBSRates: &boolTrue,
+ },
+ },
+ testOutput{
+ resultingRates: expectedRateEngineRates,
+ },
+ },
+ {
+ "nil pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates",
+ testInput{
+ pbsRates: nil,
+ bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: customRates,
+ UsePBSRates: &boolFalse,
+ },
+ },
+ testOutput{
+ resultingRates: customRates,
+ },
+ },
+ {
+ "nil pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates identical to customRates",
+ testInput{
+ pbsRates: nil,
+ bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
+ ConversionRates: customRates,
+ UsePBSRates: &boolTrue,
+ },
+ },
+ testOutput{
+ resultingRates: customRates,
+ },
+ },
+ {
+ "valid pbsRates, empty ConversionRates, false UsePBSRates. Because pbsRates cannot be used, default to constant rates",
+ testInput{
+ pbsRates: pbsRates,
+ bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
+ // ConversionRates inCustomRates not initialized makes for a zero-length map
+ UsePBSRates: &boolFalse,
+ },
+ },
+ testOutput{
+ constantRates: true,
+ },
+ },
+ {
+ "valid pbsRates, nil ConversionRates, UsePBSRates defaults to true. Resulting rates will be identical to pbsRates",
+ testInput{
+ pbsRates: pbsRates,
+ bidExtCurrency: nil,
+ },
+ testOutput{
+ resultingRates: pbsRates,
+ },
+ },
+ {
+ "nil pbsRates, empty ConversionRates, false UsePBSRates. Default to constant rates",
+ testInput{
+ pbsRates: nil,
+ bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
+ // ConversionRates inCustomRates not initialized makes for a zero-length map
+ UsePBSRates: &boolFalse,
+ },
+ },
+ testOutput{
+ constantRates: true,
+ },
+ },
+ {
+ "customRates empty, UsePBSRates set to true, pbsRates are nil. Return default constant rates converter",
+ testInput{
+ pbsRates: nil,
+ bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
+ // ConversionRates inCustomRates not initialized makes for a zero-length map
+ UsePBSRates: &boolTrue,
+ },
+ },
+ testOutput{
+ constantRates: true,
+ },
+ },
+ {
+ "nil customRates, nil pbsRates, UsePBSRates defaults to true. Return default constant rates converter",
+ testInput{
+ pbsRates: nil,
+ bidExtCurrency: nil,
+ },
+ testOutput{
+ constantRates: true,
+ },
+ },
+ }
+
+ for _, tc := range testCases {
+
+ // Test setup:
+ jsonPbsRates, err := json.Marshal(tc.given.pbsRates)
+ if err != nil {
+ t.Fatalf("Failed to marshal PBS rates: %v", err)
+ }
+
+ // Init mock currency conversion service
+ mockCurrencyClient := &fakeCurrencyRatesHttpClient{
+ responseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`,
+ }
+ mockCurrencyConverter := currency.NewRateConverter(
+ mockCurrencyClient,
+ "currency.fake.com",
+ 24*time.Hour,
+ )
+ mockCurrencyConverter.Run()
+
+ e := new(exchange)
+ e.currencyConverter = mockCurrencyConverter
+
+ // Run test
+ auctionRates := e.getAuctionCurrencyRates(tc.given.bidExtCurrency)
+
+ // When fromCurrency and toCurrency are the same, a rate of 1.00 is always expected
+ rate, err := auctionRates.GetRate("USD", "USD")
+ assert.NoError(t, err, tc.desc)
+ assert.Equal(t, float64(1), rate, tc.desc)
+
+ // If we expect an error, assert we have one along with a conversion rate of zero
+ if tc.expected.constantRates {
+ rate, err := auctionRates.GetRate("USD", "MXN")
+ assert.Error(t, err, tc.desc)
+ assert.Equal(t, float64(0), rate, tc.desc)
+ } else {
+ for fromCurrency, rates := range tc.expected.resultingRates {
+ for toCurrency, expectedRate := range rates {
+ actualRate, err := auctionRates.GetRate(fromCurrency, toCurrency)
+ assert.NoError(t, err, tc.desc)
+ assert.Equal(t, expectedRate, actualRate, tc.desc)
+ }
+ }
+ }
+ }
+}
+
func TestReturnCreativeEndToEnd(t *testing.T) {
sampleAd := ""
@@ -710,7 +1167,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
testExternalCacheHost := "www.externalprebidcache.net"
testExternalCachePath := "endpoints/cache"
- /* 1) An adapter */
+ // 1) An adapter
bidderName := openrtb_ext.BidderName("appnexus")
cfg := &config.Configuration{
@@ -731,7 +1188,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
adapterList := make([]openrtb_ext.BidderName, 0, 2)
testEngine := metricsConf.NewMetricsEngine(cfg, adapterList)
- /* 2) Init new exchange with said configuration */
+ // 2) Init new exchange with said configuration
handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
defer server.Close()
@@ -748,7 +1205,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
pbc := pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine)
e := NewExchange(adapters, pbc, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange)
- /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */
+ // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
liveAdapters := []openrtb_ext.BidderName{bidderName}
//adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid,
@@ -850,10 +1307,10 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
var errList []error
- /* 4) Build bid response */
+ // 4) Build bid response
bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, errList)
- /* 5) Assert we have no errors and the bid response we expected*/
+ // 5) Assert we have no errors and the bid response we expected
assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error")
expectedBidResponse := &openrtb2.BidResponse{
@@ -1331,7 +1788,7 @@ func newRaceCheckingRequest(t *testing.T) *openrtb2.BidRequest {
User: &openrtb2.User{
ID: "our-id",
BuyerUID: "their-id",
- Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`),
+ Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`),
},
Regs: &openrtb2.Regs{
COPPA: 1,
@@ -1493,7 +1950,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
User: &openrtb2.User{
ID: "our-id",
BuyerUID: "their-id",
- Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`),
+ Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`),
},
Imp: []openrtb2.Imp{{
ID: "some-imp-id",
@@ -1615,6 +2072,13 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
eeac[c] = s
}
+ var gdprDefaultValue string
+ if spec.AssumeGDPRApplies {
+ gdprDefaultValue = "1"
+ } else {
+ gdprDefaultValue = "0"
+ }
+
privacyConfig := config.Privacy{
CCPA: config.CCPA{
Enforce: spec.EnforceCCPA,
@@ -1623,9 +2087,9 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
Enforce: spec.EnforceLMT,
},
GDPR: config.GDPR{
- Enabled: spec.GDPREnabled,
- UsersyncIfAmbiguous: !spec.AssumeGDPRApplies,
- EEACountriesMap: eeac,
+ Enabled: spec.GDPREnabled,
+ DefaultValue: gdprDefaultValue,
+ EEACountriesMap: eeac,
},
}
bidIdGenerator := &mockBidIDGenerator{}
@@ -1766,19 +2230,24 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
t.Fatalf("Failed to create a category Fetcher: %v", error)
}
+ gdprDefaultValue := gdpr.SignalYes
+ if privacyConfig.GDPR.DefaultValue == "0" {
+ gdprDefaultValue = gdpr.SignalNo
+ }
+
return &exchange{
- adapterMap: bidderAdapters,
- me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()),
- cache: &wellBehavedCache{},
- cacheTime: 0,
- gDPR: gdpr.AlwaysFail{},
- currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
- UsersyncIfAmbiguous: privacyConfig.GDPR.UsersyncIfAmbiguous,
- privacyConfig: privacyConfig,
- categoriesFetcher: categoriesFetcher,
- bidderInfo: bidderInfos,
- externalURL: "http://localhost",
- bidIDGenerator: bidIDGenerator,
+ adapterMap: bidderAdapters,
+ me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()),
+ cache: &wellBehavedCache{},
+ cacheTime: 0,
+ gDPR: &permissionsMock{allowAllBidders: true},
+ currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
+ gdprDefaultValue: gdprDefaultValue,
+ privacyConfig: privacyConfig,
+ categoriesFetcher: categoriesFetcher,
+ bidderInfo: bidderInfos,
+ externalURL: "http://localhost",
+ bidIDGenerator: bidIDGenerator,
}
}
@@ -1801,6 +2270,14 @@ func (big *mockBidIDGenerator) New() (string, error) {
}
+type fakeRandomDeduplicateBidBooleanGenerator struct {
+ returnValue bool
+}
+
+func (m *fakeRandomDeduplicateBidBooleanGenerator) Generate() bool {
+ return m.returnValue
+}
+
func newExtRequest() openrtb_ext.ExtRequest {
priceGran := openrtb_ext.PriceGranularity{
Precision: 2,
@@ -1901,7 +2378,7 @@ func TestCategoryMapping(t *testing.T) {
adapterBids[bidderName1] = &seatBid
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
assert.Equal(t, nil, err, "Category mapping error should be empty")
assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message")
@@ -1957,7 +2434,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
adapterBids[bidderName1] = &seatBid
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
assert.Equal(t, nil, err, "Category mapping error should be empty")
assert.Empty(t, rejections, "There should be no bid rejection messages")
@@ -2010,7 +2487,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
adapterBids[bidderName1] = &seatBid
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
assert.Equal(t, nil, err, "Category mapping error should be empty")
assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message")
@@ -2093,7 +2570,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
adapterBids[bidderName1] = &seatBid
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
assert.Equal(t, nil, err, "Category mapping error should be empty")
assert.Empty(t, rejections, "There should be no bid rejection messages")
@@ -2164,7 +2641,7 @@ func TestCategoryDedupe(t *testing.T) {
adapterBids[bidderName1] = &seatBid
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
assert.Equal(t, nil, err, "Category mapping error should be empty")
assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages")
@@ -2245,7 +2722,7 @@ func TestNoCategoryDedupe(t *testing.T) {
adapterBids[bidderName1] = &seatBid
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
assert.Equal(t, nil, err, "Category mapping error should be empty")
assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages")
@@ -2311,7 +2788,7 @@ func TestCategoryMappingBidderName(t *testing.T) {
adapterBids[bidderName1] = &seatBid1
adapterBids[bidderName2] = &seatBid2
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
assert.NoError(t, err, "Category mapping error should be empty")
assert.Empty(t, rejections, "There should be 0 bid rejection messages")
@@ -2366,7 +2843,7 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) {
adapterBids[bidderName1] = &seatBid1
adapterBids[bidderName2] = &seatBid2
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
assert.NoError(t, err, "Category mapping error should be empty")
assert.Empty(t, rejections, "There should be 0 bid rejection messages")
@@ -2466,9 +2943,9 @@ func TestBidRejectionErrors(t *testing.T) {
seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"}
adapterBids[bidderName] = &seatBid
-
bidRequest := openrtb2.BidRequest{}
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &test.reqExt, adapterBids, categoriesFetcher, targData)
+
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &test.reqExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
if len(test.expectedCatDur) > 0 {
// Bid deduplication case
@@ -2532,7 +3009,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) {
adapterBids[bidderNameApn1] = &seatBidApn1
adapterBids[bidderNameApn2] = &seatBidApn2
- bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData)
+ bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{})
assert.NoError(t, err, "Category mapping error should be empty")
assert.Len(t, rejections, 1, "There should be 1 bid rejection message")
@@ -2551,9 +3028,171 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) {
} else {
assert.Nil(t, seatBidApn1.bids, "Appnexus_1 seat bid should not have any bids back")
assert.Len(t, seatBidApn2.bids, 1, "Appnexus_2 seat bid should have only one back")
+ }
+ }
+}
+
+func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) {
+ // This test covers a very rare de-duplication case where bid needs to be removed from already processed bidder
+ // This happens when current processing bidder has a bid that has same de-duplication key as a bid from already processed bidder
+ // and already processed bid was selected to be removed
+
+ //In this test case bids bid_idApn1_1 and bid_idApn1_2 will be removed due to hardcoded "fakeRandomDeduplicateBidBooleanGenerator{true}"
+
+ // Also there are should be more than one bids in bidder to test how we remove single element from bids array.
+ // In case there is just one bid to remove - we remove the entire bidder.
+ categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
+ if error != nil {
+ t.Errorf("Failed to create a category Fetcher: %v", error)
+ }
+
+ bidRequest := openrtb2.BidRequest{}
+ requestExt := newExtRequestTranslateCategories(nil)
+
+ targData := &targetData{
+ priceGranularity: requestExt.Prebid.Targeting.PriceGranularity,
+ includeWinners: true,
+ }
+
+ requestExt.Prebid.Targeting.DurationRangeSec = []int{30}
+ requestExt.Prebid.Targeting.IncludeBrandCategory.WithCategory = false
+
+ cats1 := []string{"IAB1-3"}
+ cats2 := []string{"IAB1-4"}
+
+ bidApn1_1 := openrtb2.Bid{ID: "bid_idApn1_1", ImpID: "imp_idApn1_1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
+ bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1}
+
+ bidApn2_1 := openrtb2.Bid{ID: "bid_idApn2_1", ImpID: "imp_idApn2_1", Price: 10.0000, Cat: cats2, W: 1, H: 1}
+ bidApn2_2 := openrtb2.Bid{ID: "bid_idApn2_2", ImpID: "imp_idApn2_2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
+
+ bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+ bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+
+ bid1_Apn2_1 := pbsOrtbBid{&bidApn2_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+ bid1_Apn2_2 := pbsOrtbBid{&bidApn2_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+
+ innerBidsApn1 := []*pbsOrtbBid{
+ &bid1_Apn1_1,
+ &bid1_Apn1_2,
+ }
+
+ innerBidsApn2 := []*pbsOrtbBid{
+ &bid1_Apn2_1,
+ &bid1_Apn2_2,
+ }
+
+ adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid)
+
+ seatBidApn1 := pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"}
+ bidderNameApn1 := openrtb_ext.BidderName("appnexus1")
+
+ seatBidApn2 := pbsOrtbSeatBid{bids: innerBidsApn2, currency: "USD"}
+ bidderNameApn2 := openrtb_ext.BidderName("appnexus2")
+
+ adapterBids[bidderNameApn1] = &seatBidApn1
+ adapterBids[bidderNameApn2] = &seatBidApn2
+
+ _, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &fakeRandomDeduplicateBidBooleanGenerator{true})
+
+ assert.NoError(t, err, "Category mapping error should be empty")
+
+ //Total number of bids from all bidders in this case should be 2
+ bidsFromFirstBidder := adapterBids[bidderNameApn1]
+ bidsFromSecondBidder := adapterBids[bidderNameApn2]
+
+ totalNumberOfbids := 0
+
+ //due to random map order we need to identify what bidder was first
+ firstBidderIndicator := true
+
+ if bidsFromFirstBidder.bids != nil {
+ totalNumberOfbids += len(bidsFromFirstBidder.bids)
+ }
+
+ if bidsFromSecondBidder.bids != nil {
+ firstBidderIndicator = false
+ totalNumberOfbids += len(bidsFromSecondBidder.bids)
+ }
+
+ assert.Equal(t, 2, totalNumberOfbids, "2 bids total should be returned")
+ assert.Len(t, rejections, 2, "2 bids should be de-duplicated")
+
+ if firstBidderIndicator {
+ assert.Len(t, adapterBids[bidderNameApn1].bids, 2)
+ assert.Len(t, adapterBids[bidderNameApn2].bids, 0)
+
+ assert.Equal(t, "bid_idApn1_1", adapterBids[bidderNameApn1].bids[0].bid.ID, "Incorrect expected bid 1 id")
+ assert.Equal(t, "bid_idApn1_2", adapterBids[bidderNameApn1].bids[1].bid.ID, "Incorrect expected bid 2 id")
+
+ assert.Equal(t, "bid rejected [bid ID: bid_idApn2_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1")
+ assert.Equal(t, "bid rejected [bid ID: bid_idApn2_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2")
+
+ } else {
+ assert.Len(t, adapterBids[bidderNameApn1].bids, 0)
+ assert.Len(t, adapterBids[bidderNameApn2].bids, 2)
+
+ assert.Equal(t, "bid_idApn2_1", adapterBids[bidderNameApn2].bids[0].bid.ID, "Incorrect expected bid 1 id")
+ assert.Equal(t, "bid_idApn2_2", adapterBids[bidderNameApn2].bids[1].bid.ID, "Incorrect expected bid 2 id")
+
+ assert.Equal(t, "bid rejected [bid ID: bid_idApn1_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1")
+ assert.Equal(t, "bid rejected [bid ID: bid_idApn1_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2")
+
+ }
+}
+
+func TestRemoveBidById(t *testing.T) {
+ cats1 := []string{"IAB1-3"}
+
+ bidApn1_1 := openrtb2.Bid{ID: "bid_idApn1_1", ImpID: "imp_idApn1_1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
+ bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1}
+ bidApn1_3 := openrtb2.Bid{ID: "bid_idApn1_3", ImpID: "imp_idApn1_3", Price: 10.0000, Cat: cats1, W: 1, H: 1}
+
+ bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+ bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+ bid1_Apn1_3 := pbsOrtbBid{&bidApn1_3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+
+ type aTest struct {
+ desc string
+ inBidName string
+ outBids []*pbsOrtbBid
+ }
+ testCases := []aTest{
+ {
+ desc: "remove element from the middle",
+ inBidName: "bid_idApn1_2",
+ outBids: []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_3},
+ },
+ {
+ desc: "remove element from the end",
+ inBidName: "bid_idApn1_3",
+ outBids: []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2},
+ },
+ {
+ desc: "remove element from the beginning",
+ inBidName: "bid_idApn1_1",
+ outBids: []*pbsOrtbBid{&bid1_Apn1_2, &bid1_Apn1_3},
+ },
+ {
+ desc: "remove element that doesn't exist",
+ inBidName: "bid_idApn",
+ outBids: []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2, &bid1_Apn1_3},
+ },
+ }
+ for _, test := range testCases {
+
+ innerBidsApn1 := []*pbsOrtbBid{
+ &bid1_Apn1_1,
+ &bid1_Apn1_2,
+ &bid1_Apn1_3,
}
+ seatBidApn1 := &pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"}
+
+ removeBidById(seatBidApn1, test.inBidName)
+ assert.Len(t, seatBidApn1.bids, len(test.outBids), test.desc)
+ assert.ElementsMatch(t, seatBidApn1.bids, test.outBids, "Incorrect bids in response")
}
}
@@ -2893,7 +3532,7 @@ type validatingBidder struct {
mockResponses map[string]bidderResponse
}
-func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) {
+func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) {
if expectedRequest, ok := b.expectations[string(name)]; ok {
if expectedRequest != nil {
if expectedRequest.BidAdjustment != bidAdjustment {
@@ -3072,7 +3711,7 @@ func (e *mockUsersync) LiveSyncCount() int {
type panicingAdapter struct{}
-func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) {
+func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) {
panic("Panic! Panic! The world is ending!")
}
@@ -3094,6 +3733,36 @@ func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer,
return "", nil
}
+// fakeCurrencyRatesHttpClient is a simple http client mock returning a constant response body
+type fakeCurrencyRatesHttpClient struct {
+ responseBody string
+}
+
+func (m *fakeCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, error) {
+ return &http.Response{
+ Status: "200 OK",
+ StatusCode: http.StatusOK,
+ Body: ioutil.NopCloser(strings.NewReader(m.responseBody)),
+ }, nil
+}
+
+type mockBidder struct {
+ mock.Mock
+ lastExtraRequestInfo *adapters.ExtraRequestInfo
+}
+
+func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ m.lastExtraRequestInfo = reqInfo
+
+ args := m.Called(request, reqInfo)
+ return args.Get(0).([]*adapters.RequestData), args.Get(1).([]error)
+}
+
+func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ args := m.Called(internalRequest, externalRequest, response)
+ return args.Get(0).(*adapters.BidderResponse), args.Get(1).([]error)
+}
+
//TestApplyAdvertiserBlocking verifies advertiser blocking
//Currently it is expected to work only with TagBidders and not woth
// normal bidders
diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json
index 851bda69097..8475482f35b 100644
--- a/exchange/exchangetest/debuglog_enabled.json
+++ b/exchange/exchangetest/debuglog_enabled.json
@@ -1,6 +1,8 @@
{
"debugLog": {
"Enabled": true,
+ "DebugEnabledOrOverridden": true,
+ "DebugOverride": false,
"CacheType": "xml",
"TTL": 3600,
"Data": {
diff --git a/exchange/exchangetest/debuglog_enabled_no_bids.json b/exchange/exchangetest/debuglog_enabled_no_bids.json
index 4823acf8f16..b9bb15df7fb 100644
--- a/exchange/exchangetest/debuglog_enabled_no_bids.json
+++ b/exchange/exchangetest/debuglog_enabled_no_bids.json
@@ -1,6 +1,8 @@
{
"debugLog": {
"Enabled": true,
+ "DebugEnabledOrOverridden": true,
+ "DebugOverride": false,
"CacheType": "xml",
"TTL": 3600,
"Data": {
diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json
similarity index 100%
rename from exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json
rename to exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json
diff --git a/exchange/exchangetest/request-other-user-ext.json b/exchange/exchangetest/request-other-user-ext.json
index 9bd4c02fb42..f9fb3264c3c 100644
--- a/exchange/exchangetest/request-other-user-ext.json
+++ b/exchange/exchangetest/request-other-user-ext.json
@@ -12,11 +12,6 @@
"buyeruids": {
"appnexus": "explicit-appnexus"
}
- },
- "digitrust": {
- "id": "digi-id",
- "keyv": 1,
- "pref": 2
}
}
},
@@ -48,14 +43,7 @@
},
"user": {
"id": "foo",
- "buyeruid": "explicit-appnexus",
- "ext": {
- "digitrust": {
- "id": "digi-id",
- "keyv": 1,
- "pref": 2
- }
- }
+ "buyeruid": "explicit-appnexus"
},
"imp": [
{
diff --git a/exchange/exchangetest/request-user-no-prebid.json b/exchange/exchangetest/request-user-no-prebid.json
index bb36ba8aeeb..aae11606baa 100644
--- a/exchange/exchangetest/request-user-no-prebid.json
+++ b/exchange/exchangetest/request-user-no-prebid.json
@@ -8,11 +8,6 @@
"user": {
"id": "foo",
"ext": {
- "digitrust": {
- "id": "digi-id",
- "keyv": 1,
- "pref": 2
- }
}
},
"imp": [
@@ -45,11 +40,6 @@
"id": "foo",
"buyeruid": "implicit-appnexus",
"ext": {
- "digitrust": {
- "id": "digi-id",
- "keyv": 1,
- "pref": 2
- }
}
},
"imp": [
diff --git a/exchange/legacy.go b/exchange/legacy.go
deleted file mode 100644
index 0e7d1590686..00000000000
--- a/exchange/legacy.go
+++ /dev/null
@@ -1,375 +0,0 @@
-package exchange
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
-
- "github.com/buger/jsonparser"
- "github.com/mxmCherry/openrtb/v15/openrtb2"
- "github.com/prebid/prebid-server/adapters"
- "github.com/prebid/prebid-server/currency"
- "github.com/prebid/prebid-server/openrtb_ext"
- "github.com/prebid/prebid-server/pbs"
- "github.com/prebid/prebid-server/usersync"
-)
-
-// AdaptLegacyAdapter turns a bidder.Adapter into an adaptedBidder.
-//
-// This is a temporary function which helps make the transition to OpenRTB smooth. Bidders which have not been
-// updated yet can use this to be "OpenRTB-ish". They'll bid as well as they can, given the limitations of the
-// legacy protocol
-func adaptLegacyAdapter(adapter adapters.Adapter) adaptedBidder {
- return &adaptedAdapter{
- adapter: adapter,
- }
-}
-
-type adaptedAdapter struct {
- adapter adapters.Adapter
-}
-
-// requestBid attempts to bid on OpenRTB requests using the legacy protocol.
-//
-// This is not ideal. OpenRTB provides a superset of the legacy data structures.
-// For requests which use those features, the best we can do is respond with "no bid".
-func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
- legacyRequest, legacyBidder, errs := bidder.toLegacyAdapterInputs(request, name)
- if legacyRequest == nil || legacyBidder == nil {
- return nil, errs
- }
-
- legacyBids, err := bidder.adapter.Call(ctx, legacyRequest, legacyBidder)
- if err != nil {
- errs = append(errs, err)
- }
-
- for i := 0; i < len(legacyBids); i++ {
- legacyBids[i].Price = legacyBids[i].Price * bidAdjustment
- }
-
- finalResponse, moreErrs := toNewResponse(legacyBids, legacyBidder, name)
- return finalResponse, append(errs, moreErrs...)
-}
-
-// ----------------------------------------------------------------------------
-// Request transformations.
-
-// toLegacyAdapterInputs is a best-effort transformation of an OpenRTB BidRequest into the args needed to run a legacy Adapter.
-// If the OpenRTB request is too complex, it fails with an error.
-// If the error is nil, then the PBSRequest and PBSBidder are valid.
-func (bidder *adaptedAdapter) toLegacyAdapterInputs(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSRequest, *pbs.PBSBidder, []error) {
- legacyReq, err := bidder.toLegacyRequest(req)
- if err != nil {
- return nil, nil, []error{err}
- }
-
- legacyBidder, errs := toLegacyBidder(req, name)
- if legacyBidder == nil {
- return nil, nil, errs
- }
-
- return legacyReq, legacyBidder, errs
-}
-
-func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb2.BidRequest) (*pbs.PBSRequest, error) {
- acctId, err := toAccountId(req)
- if err != nil {
- return nil, err
- }
-
- tId, err := toTransactionId(req)
- if err != nil {
- return nil, err
- }
-
- isSecure, err := toSecure(req)
- if err != nil {
- return nil, err
- }
-
- isDebug := false
- var requestExt openrtb_ext.ExtRequest
- if req.Ext != nil {
- err = json.Unmarshal(req.Ext, &requestExt)
- if err != nil {
- return nil, fmt.Errorf("Error decoding Request.ext : %s", err.Error())
- }
- }
-
- if requestExt.Prebid.Debug {
- isDebug = true
- }
-
- url := ""
- domain := ""
- if req.Site != nil {
- url = req.Site.Page
- domain = req.Site.Domain
- }
-
- cookie := usersync.NewPBSCookie()
- if req.User != nil {
- if req.User.BuyerUID != "" {
- cookie.TrySync(bidder.adapter.Name(), req.User.BuyerUID)
- }
-
- // This shouldn't be appnexus-specific... but this line does correctly invert the
- // logic from adapters/openrtb_util.go, which will preserve this questionable behavior in legacy adapters.
- if req.User.ID != "" {
- cookie.TrySync("adnxs", req.User.ID)
- }
- }
-
- return &pbs.PBSRequest{
- AccountID: acctId,
- Tid: tId,
- // CacheMarkup is excluded because no legacy adapters read from it
- // SortBids is excluded because no legacy adapters read from it
- // MaxKeyLength is excluded because no legacy adapters read from it
- Secure: isSecure,
- TimeoutMillis: req.TMax,
- // AdUnits is excluded because no legacy adapters read from it
- IsDebug: isDebug,
- App: req.App,
- Device: req.Device,
- // PBSUser is excluded because rubicon is the only adapter which reads from it, and they're supporting OpenRTB directly
- // SDK is excluded because that information doesn't exist in openrtb2.
- // Bidders is excluded because no legacy adapters read from it
- User: req.User,
- Cookie: cookie,
- Url: url,
- Domain: domain,
- // Start is excluded because no legacy adapters read from it
- Regs: req.Regs,
- }, nil
-}
-
-func toAccountId(req *openrtb2.BidRequest) (string, error) {
- if req.Site != nil && req.Site.Publisher != nil {
- return req.Site.Publisher.ID, nil
- }
- if req.App != nil && req.App.Publisher != nil {
- return req.App.Publisher.ID, nil
- }
- return "", errors.New("bidrequest.site.publisher.id or bidrequest.app.publisher.id required for legacy bidders.")
-}
-
-func toTransactionId(req *openrtb2.BidRequest) (string, error) {
- if req.Source != nil {
- return req.Source.TID, nil
- }
- return "", errors.New("bidrequest.source.tid required for legacy bidders.")
-}
-
-func toSecure(req *openrtb2.BidRequest) (secure int8, err error) {
- secure = -1
- for _, imp := range req.Imp {
- if imp.Secure != nil {
- thisVal := *imp.Secure
- if thisVal == 0 {
- if secure == 1 {
- err = errors.New("bidrequest.imp[i].secure must be consistent for legacy bidders. Mixing 0 and 1 are not allowed.")
- return
- }
- secure = 0
- } else if thisVal == 1 {
- if secure == 0 {
- err = errors.New("bidrequest.imp[i].secure must be consistent for legacy bidders. Mixing 0 and 1 are not allowed.")
- return
- }
- secure = 1
- }
- }
- }
- if secure == -1 {
- secure = 0
- }
-
- return
-}
-
-func toLegacyBidder(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSBidder, []error) {
- adUnits, errs := toPBSAdUnits(req)
- if len(adUnits) > 0 {
- return &pbs.PBSBidder{
- BidderCode: string(name),
- // AdUnitCode is excluded because no legacy adapters read from it
- // ResponseTime is excluded because no legacy adapters read from it
- // NumBids is excluded because no legacy adapters read from it
- // Error is excluded because no legacy adapters read from it
- // NoCookie is excluded because no legacy adapters read from it
- // NoBid is excluded because no legacy adapters read from it
- // UsersyncInfo is excluded because no legacy adapters read from it
- // Debug is excluded because legacy adapters only use it in nil-safe ways.
- // They *do* write to it, though, so it may be read when unpacking the response.
- AdUnits: adUnits,
- }, errs
- } else {
- return nil, errs
- }
-}
-
-func toPBSAdUnits(req *openrtb2.BidRequest) ([]pbs.PBSAdUnit, []error) {
- adUnits := make([]pbs.PBSAdUnit, len(req.Imp))
- var errs []error = nil
- nextAdUnit := 0
- for i := 0; i < len(req.Imp); i++ {
- err := initPBSAdUnit(&(req.Imp[i]), &(adUnits[nextAdUnit]))
- if err != nil {
- errs = append(errs, err)
- } else {
- nextAdUnit++
- }
- }
- return adUnits[:nextAdUnit], errs
-}
-
-func initPBSAdUnit(imp *openrtb2.Imp, adUnit *pbs.PBSAdUnit) error {
- var sizes []openrtb2.Format = nil
-
- video := pbs.PBSVideo{}
- if imp.Video != nil {
- video.Mimes = imp.Video.MIMEs
- video.Minduration = imp.Video.MinDuration
- video.Maxduration = imp.Video.MaxDuration
- if imp.Video.StartDelay != nil {
- video.Startdelay = int64(*imp.Video.StartDelay)
- }
- if imp.Video.Skip != nil {
- video.Skippable = int(*imp.Video.Skip)
- }
- if len(imp.Video.PlaybackMethod) == 1 {
- video.PlaybackMethod = int8(imp.Video.PlaybackMethod[0])
- }
- if len(imp.Video.Protocols) > 0 {
- video.Protocols = make([]int8, len(imp.Video.Protocols))
- for i := 0; i < len(imp.Video.Protocols); i++ {
- video.Protocols[i] = int8(imp.Video.Protocols[i])
- }
- }
- // Fixes #360
- if imp.Video.W != 0 && imp.Video.H != 0 {
- sizes = append(sizes, openrtb2.Format{
- W: imp.Video.W,
- H: imp.Video.H,
- })
- }
- }
- topFrame := int8(0)
- if imp.Banner != nil {
- topFrame = imp.Banner.TopFrame
- sizes = append(sizes, imp.Banner.Format...)
- }
-
- params, _, _, err := jsonparser.Get(imp.Ext, "bidder")
- if err != nil {
- return err
- }
-
- mediaTypes := make([]pbs.MediaType, 0, 2)
- if imp.Banner != nil {
- mediaTypes = append(mediaTypes, pbs.MEDIA_TYPE_BANNER)
- }
- if imp.Video != nil {
- mediaTypes = append(mediaTypes, pbs.MEDIA_TYPE_VIDEO)
- }
- if len(mediaTypes) == 0 {
- return errors.New("legacy bidders can only bid on banner and video ad units")
- }
-
- adUnit.Sizes = sizes
- adUnit.TopFrame = topFrame
- adUnit.Code = imp.ID
- adUnit.BidID = imp.ID
- adUnit.Params = json.RawMessage(params)
- adUnit.Video = video
- adUnit.MediaTypes = mediaTypes
- adUnit.Instl = imp.Instl
-
- return nil
-}
-
-// ----------------------------------------------------------------------------
-// Response transformations.
-
-// toNewResponse is a best-effort transformation of legacy Bids into an OpenRTB response.
-func toNewResponse(bids pbs.PBSBidSlice, bidder *pbs.PBSBidder, name openrtb_ext.BidderName) (*pbsOrtbSeatBid, []error) {
- newBids, errs := transformBids(bids)
- return &pbsOrtbSeatBid{
- bids: newBids,
- httpCalls: transformDebugs(bidder.Debug),
- }, errs
-}
-
-func transformBids(legacyBids pbs.PBSBidSlice) ([]*pbsOrtbBid, []error) {
- newBids := make([]*pbsOrtbBid, 0, len(legacyBids))
- var errs []error
- for _, legacyBid := range legacyBids {
- if legacyBid != nil {
- newBid, err := transformBid(legacyBid)
- if err == nil {
- newBids = append(newBids, newBid)
- } else {
- errs = append(errs, err)
- }
- }
- }
- return newBids, errs
-}
-
-func transformBid(legacyBid *pbs.PBSBid) (*pbsOrtbBid, error) {
- newBid := transformBidToOrtb(legacyBid)
-
- newBidType, err := openrtb_ext.ParseBidType(legacyBid.CreativeMediaType)
- if err != nil {
- return nil, err
- }
-
- return &pbsOrtbBid{
- bid: newBid,
- bidType: newBidType,
- }, nil
-}
-
-func transformBidToOrtb(legacyBid *pbs.PBSBid) *openrtb2.Bid {
- return &openrtb2.Bid{
- ID: legacyBid.BidID,
- ImpID: legacyBid.AdUnitCode,
- CrID: legacyBid.Creative_id,
- // legacyBid.CreativeMediaType is handled by transformBid(), because it doesn't exist on the openrtb2.Bid
- // legacyBid.BidderCode is handled by the exchange, which already knows which bidder we are.
- // legacyBid.BidHash is ignored, because it doesn't get sent in the response anyway
- Price: legacyBid.Price,
- NURL: legacyBid.NURL,
- AdM: legacyBid.Adm,
- W: legacyBid.Width,
- H: legacyBid.Height,
- DealID: legacyBid.DealId,
- // TODO #216: Support CacheID here
- // TODO: #216: Support CacheURL here
- // ResponseTime is handled by the exchange, since it doesn't exist in the OpenRTB Bid
- // AdServerTargeting is handled by the exchange. Rubicon's adapter is the only one which writes to it,
- // but that doesn't matter since they're supporting OpenRTB directly.
- }
-}
-
-func transformDebugs(legacyDebugs []*pbs.BidderDebug) []*openrtb_ext.ExtHttpCall {
- newDebug := make([]*openrtb_ext.ExtHttpCall, 0, len(legacyDebugs))
- for _, legacyDebug := range legacyDebugs {
- if legacyDebug != nil {
- newDebug = append(newDebug, transformDebug(legacyDebug))
- }
- }
- return newDebug
-}
-
-func transformDebug(legacyDebug *pbs.BidderDebug) *openrtb_ext.ExtHttpCall {
- return &openrtb_ext.ExtHttpCall{
- Uri: legacyDebug.RequestURI,
- RequestBody: legacyDebug.RequestBody,
- ResponseBody: legacyDebug.ResponseBody,
- Status: legacyDebug.StatusCode,
- }
-}
diff --git a/exchange/legacy_test.go b/exchange/legacy_test.go
deleted file mode 100644
index cbb5fda4fcc..00000000000
--- a/exchange/legacy_test.go
+++ /dev/null
@@ -1,505 +0,0 @@
-package exchange
-
-import (
- "context"
- "encoding/json"
- "errors"
- "net/http"
- "reflect"
- "testing"
- "time"
-
- "github.com/buger/jsonparser"
- jsonpatch "github.com/evanphx/json-patch"
- "github.com/mxmCherry/openrtb/v15/openrtb2"
- "github.com/prebid/prebid-server/adapters"
- "github.com/prebid/prebid-server/currency"
- "github.com/prebid/prebid-server/openrtb_ext"
- "github.com/prebid/prebid-server/pbs"
- "github.com/prebid/prebid-server/usersync"
-)
-
-func TestSiteVideo(t *testing.T) {
- ortbRequest := &openrtb2.BidRequest{
- ID: "request-id",
- TMax: 1000,
- Site: &openrtb2.Site{
- Page: "http://www.site.com",
- Domain: "site.com",
- Publisher: &openrtb2.Publisher{
- ID: "b1c81a38-1415-42b7-8238-0d2d64016c27",
- },
- },
- Source: &openrtb2.Source{
- TID: "transaction-id",
- },
- User: &openrtb2.User{
- ID: "host-id",
- BuyerUID: "bidder-id",
- },
- Test: 1,
- Imp: []openrtb2.Imp{{
- ID: "imp-id",
- Video: &openrtb2.Video{
- MIMEs: []string{"video/mp4"},
- MinDuration: 20,
- MaxDuration: 40,
- Protocols: []openrtb2.Protocol{openrtb2.ProtocolVAST10},
- StartDelay: openrtb2.StartDelayGenericMidRoll.Ptr(),
- },
- Banner: &openrtb2.Banner{
- Format: []openrtb2.Format{{
- W: 300,
- H: 250,
- }},
- },
- Ext: json.RawMessage(`{"bidder":{"cp":512379,"ct":486653,"cf":"300x250"}}`),
- }},
- }
-
- mockAdapter := mockLegacyAdapter{}
-
- exchangeBidder := adaptLegacyAdapter(&mockAdapter)
- currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
- if len(errs) > 0 {
- t.Errorf("Unexpected error requesting bids: %v", errs)
- }
-
- if mockAdapter.gotRequest == nil {
- t.Fatalf("Mock adapter never received a request.")
- }
-
- if mockAdapter.gotBidder == nil {
- t.Fatalf("Mock adapter never received a bidder.")
- }
-
- assertEquivalentRequests(t, ortbRequest, mockAdapter.gotRequest)
-
- if mockAdapter.gotBidder.BidderCode != string(openrtb_ext.BidderRubicon) {
- t.Errorf("Wrong bidder code. Expected %s, got %s", string(openrtb_ext.BidderRubicon), mockAdapter.gotBidder.BidderCode)
- }
- assertEquivalentBidder(t, ortbRequest, mockAdapter.gotBidder)
-}
-
-func TestAppBanner(t *testing.T) {
- ortbRequest := newAppOrtbRequest()
- ortbRequest.TMax = 1000
- ortbRequest.User = &openrtb2.User{
- ID: "host-id",
- BuyerUID: "bidder-id",
- }
- ortbRequest.Test = 1
-
- mockAdapter := mockLegacyAdapter{}
-
- exchangeBidder := adaptLegacyAdapter(&mockAdapter)
- currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
- if len(errs) > 0 {
- t.Errorf("Unexpected error requesting bids: %v", errs)
- }
-
- if mockAdapter.gotRequest == nil {
- t.Fatalf("Mock adapter never received a request.")
- }
-
- if mockAdapter.gotBidder == nil {
- t.Fatalf("Mock adapter never received a bidder.")
- }
- if mockAdapter.gotBidder.BidderCode != string(openrtb_ext.BidderRubicon) {
- t.Errorf("Wrong bidder code. Expected %s, got %s", string(openrtb_ext.BidderRubicon), mockAdapter.gotBidder.BidderCode)
- }
-
- assertEquivalentRequests(t, ortbRequest, mockAdapter.gotRequest)
- assertEquivalentBidder(t, ortbRequest, mockAdapter.gotBidder)
-}
-
-func TestBidTransforms(t *testing.T) {
- bidAdjustment := 0.3
- initialBidPrice := 0.5
- legalBid := &pbs.PBSBid{
- BidID: "bid-1",
- AdUnitCode: "adunit-1",
- Creative_id: "creative-1",
- CreativeMediaType: "banner",
- Price: initialBidPrice,
- NURL: "nurl",
- Adm: "ad-markup",
- Width: 10,
- Height: 20,
- DealId: "some-deal",
- }
- mockAdapter := mockLegacyAdapter{
- returnedBids: pbs.PBSBidSlice{
- legalBid,
- &pbs.PBSBid{
- CreativeMediaType: "unsupported",
- },
- },
- }
-
- exchangeBidder := adaptLegacyAdapter(&mockAdapter)
- currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- seatBid, errs := exchangeBidder.requestBid(context.Background(), newAppOrtbRequest(), openrtb_ext.BidderRubicon, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
- if len(errs) != 1 {
- t.Fatalf("Bad error count. Expected 1, got %d", len(errs))
- }
- if errs[0].Error() != "invalid BidType: unsupported" {
- t.Errorf("Unexpected error message. Got %s", errs[0].Error())
- }
-
- if len(seatBid.bids) != 1 {
- t.Fatalf("Bad bid count. Expected 1, got %d", len(seatBid.bids))
- }
- theBid := seatBid.bids[0]
- if theBid.bidType != openrtb_ext.BidTypeBanner {
- t.Errorf("Bad BidType. Expected banner, got %s", theBid.bidType)
- }
- if theBid.bid.ID != legalBid.BidID {
- t.Errorf("Bad id. Expected %s, got %s", legalBid.NURL, theBid.bid.NURL)
- }
- if theBid.bid.ImpID != legalBid.AdUnitCode {
- t.Errorf("Bad impid. Expected %s, got %s", legalBid.AdUnitCode, theBid.bid.ImpID)
- }
- if theBid.bid.CrID != legalBid.Creative_id {
- t.Errorf("Bad creativeid. Expected %s, got %s", legalBid.Creative_id, theBid.bid.CrID)
- }
- if theBid.bid.Price != initialBidPrice*bidAdjustment {
- t.Errorf("Bad price. Expected %f, got %f", initialBidPrice*bidAdjustment, theBid.bid.Price)
- }
- if theBid.bid.NURL != legalBid.NURL {
- t.Errorf("Bad NURL. Expected %s, got %s", legalBid.NURL, theBid.bid.NURL)
- }
- if theBid.bid.AdM != legalBid.Adm {
- t.Errorf("Bad adm. Expected %s, got %s", legalBid.Adm, theBid.bid.AdM)
- }
- if theBid.bid.W != legalBid.Width {
- t.Errorf("Bad adm. Expected %d, got %d", legalBid.Width, theBid.bid.W)
- }
- if theBid.bid.H != legalBid.Height {
- t.Errorf("Bad adm. Expected %d, got %d", legalBid.Height, theBid.bid.H)
- }
- if theBid.bid.DealID != legalBid.DealId {
- t.Errorf("Bad dealid. Expected %s, got %s", legalBid.DealId, theBid.bid.DealID)
- }
-}
-
-func TestInsecureImps(t *testing.T) {
- insecure := int8(0)
- bidReq := &openrtb2.BidRequest{
- Imp: []openrtb2.Imp{{
- Secure: &insecure,
- }, {
- Secure: &insecure,
- }},
- }
- isSecure, err := toSecure(bidReq)
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if isSecure != 0 {
- t.Errorf("Final request should be insecure. Got %d", isSecure)
- }
-}
-
-func TestSecureImps(t *testing.T) {
- secure := int8(1)
- bidReq := &openrtb2.BidRequest{
- Imp: []openrtb2.Imp{{
- Secure: &secure,
- }, {
- Secure: &secure,
- }},
- }
- isSecure, err := toSecure(bidReq)
- if err != nil {
- t.Errorf("Unexpected error: %v", err)
- }
- if isSecure != 1 {
- t.Errorf("Final request should be secure. Got %d", isSecure)
- }
-}
-
-func TestMixedSecureImps(t *testing.T) {
- insecure := int8(0)
- secure := int8(1)
- bidReq := &openrtb2.BidRequest{
- Imp: []openrtb2.Imp{{
- Secure: &insecure,
- }, {
- Secure: &secure,
- }},
- }
- _, err := toSecure(bidReq)
- if err == nil {
- t.Error("No error was received, but we should have gotten one.")
- }
-}
-
-func newAppOrtbRequest() *openrtb2.BidRequest {
- return &openrtb2.BidRequest{
- ID: "request-id",
- App: &openrtb2.App{
- Publisher: &openrtb2.Publisher{
- ID: "b1c81a38-1415-42b7-8238-0d2d64016c27",
- },
- },
- Source: &openrtb2.Source{
- TID: "transaction-id",
- },
- Imp: []openrtb2.Imp{{
- ID: "imp-id",
- Banner: &openrtb2.Banner{
- Format: []openrtb2.Format{{
- W: 300,
- H: 250,
- }},
- },
- Ext: json.RawMessage(`{"bidder":{"cp":512379,"ct":486653,"cf":"300x250"}}`),
- }},
- }
-}
-
-func TestErrorResponse(t *testing.T) {
- ortbRequest := &openrtb2.BidRequest{
- ID: "request-id",
- App: &openrtb2.App{
- Publisher: &openrtb2.Publisher{
- ID: "b1c81a38-1415-42b7-8238-0d2d64016c27",
- },
- },
- Source: &openrtb2.Source{
- TID: "transaction-id",
- },
- Imp: []openrtb2.Imp{{
- ID: "imp-id",
- Banner: &openrtb2.Banner{
- Format: []openrtb2.Format{{
- W: 300,
- H: 250,
- }},
- },
- Ext: json.RawMessage(`{"bidder":{"cp":512379,"ct":486653,"cf":"300x250"}}`),
- }},
- }
-
- mockAdapter := mockLegacyAdapter{
- returnedError: errors.New("adapter failed"),
- }
-
- exchangeBidder := adaptLegacyAdapter(&mockAdapter)
- currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
- if len(errs) != 1 {
- t.Fatalf("Bad error count. Expected 1, got %d", len(errs))
- }
- if errs[0].Error() != "adapter failed" {
- t.Errorf("Unexpected error message. Got %s", errs[0].Error())
- }
-}
-
-func TestWithTargeting(t *testing.T) {
- ortbRequest := &openrtb2.BidRequest{
- ID: "request-id",
- App: &openrtb2.App{
- Publisher: &openrtb2.Publisher{
- ID: "b1c81a38-1415-42b7-8238-0d2d64016c27",
- },
- },
- Source: &openrtb2.Source{
- TID: "transaction-id",
- },
- Imp: []openrtb2.Imp{{
- ID: "imp-id",
- Banner: &openrtb2.Banner{
- Format: []openrtb2.Format{{
- W: 300,
- H: 250,
- }},
- },
- Ext: json.RawMessage(`{"bidder": {"placementId": "1959066997713356_1959836684303054"}}`),
- }},
- }
-
- mockAdapter := mockLegacyAdapter{
- returnedBids: []*pbs.PBSBid{{
- CreativeMediaType: "banner",
- }},
- }
- exchangeBidder := adaptLegacyAdapter(&mockAdapter)
- currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
- bid, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderAudienceNetwork, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
- if len(errs) != 0 {
- t.Fatalf("This should not produce errors. Got %v", errs)
- }
- if len(bid.bids) != 1 {
- t.Fatalf("We should get one bid back.")
- }
- if bid.bids[0] == nil {
- t.Errorf("The returned bid should not be nil.")
- }
-}
-
-// assertEquivalentFields compares the OpenRTB request with the legacy request, using the mappings defined here:
-// https://gist.github.com/dbemiller/68aa3387189fa17d3addfb9818dd4d97
-func assertEquivalentRequests(t *testing.T, req *openrtb2.BidRequest, legacy *pbs.PBSRequest) {
- if req.Site != nil {
- if req.Site.Publisher.ID != legacy.AccountID {
- t.Errorf("Account ID did not translate. OpenRTB: %s, Legacy: %s.", req.Site.Publisher.ID, legacy.AccountID)
- }
- if req.Site.Page != legacy.Url {
- t.Errorf("url did not translate. OpenRTB: %v, Legacy: %v.", req.Site.Page, legacy.Url)
- }
- if req.Site.Domain != legacy.Domain {
- t.Errorf("domain did not translate. OpenRTB: %v, Legacy: %v.", req.Site.Domain, legacy.Domain)
- }
- } else if req.App != nil {
- if req.App.Publisher.ID != legacy.AccountID {
- t.Errorf("Account ID did not translate. OpenRTB: %s, Legacy: %s.", req.Site.Publisher.ID, legacy.AccountID)
- }
- } else {
- t.Errorf("req.site and req.app are nil. This request was invalid.")
- }
-
- if req.Source.TID != legacy.Tid {
- t.Errorf("TID did not translate. OpenRTB: %s, Legacy: %s.", req.Source.TID, legacy.Tid)
- }
-
- expectedSecure := int8(0)
- if req.Imp[0].Secure != nil {
- expectedSecure = int8(*req.Imp[0].Secure)
- }
-
- if expectedSecure != legacy.Secure {
- t.Errorf("tmax did not translate. OpenRTB: %d, Legacy: %d.", expectedSecure, legacy.Secure)
- }
- // TODO: Secure
-
- if req.TMax != legacy.TimeoutMillis {
- t.Errorf("tmax did not translate. OpenRTB: %d, Legacy: %d.", req.TMax, legacy.TimeoutMillis)
- }
-
- if req.App != legacy.App {
- t.Errorf("app did not translate. OpenRTB: %v, Legacy: %v.", req.App, legacy.App)
- }
- if req.Device != legacy.Device {
- t.Errorf("device did not translate. OpenRTB: %v, Legacy: %v.", req.Device, legacy.Device)
- }
- if req.User != legacy.User {
- t.Errorf("user did not translate. OpenRTB: %v, Legacy: %v.", req.User, legacy.User)
- }
- if req.User != nil {
- if id, _, _ := legacy.Cookie.GetUID("someFamily"); id != req.User.BuyerUID {
- t.Errorf("bidder usersync did not translate. OpenRTB: %v, Legacy: %v.", req.User.BuyerUID, id)
- }
- if id, _, _ := legacy.Cookie.GetUID("adnxs"); id != req.User.ID {
- t.Errorf("user ID did not translate. OpenRTB: %v, Legacy: %v.", req.User.ID, id)
- }
- }
-}
-
-func assertEquivalentBidder(t *testing.T, req *openrtb2.BidRequest, legacy *pbs.PBSBidder) {
- if len(req.Imp) != len(legacy.AdUnits) {
- t.Errorf("Wrong number of Imps. Expected %d, got %d", len(req.Imp), len(legacy.AdUnits))
- return
- }
- for i := 0; i < len(req.Imp); i++ {
- assertEquivalentImp(t, i, &req.Imp[i], &legacy.AdUnits[i])
- }
-}
-
-func assertEquivalentImp(t *testing.T, index int, imp *openrtb2.Imp, legacy *pbs.PBSAdUnit) {
- if imp.ID != legacy.BidID {
- t.Errorf("imp[%d].id did not translate. OpenRTB %s, legacy %s", index, imp.ID, legacy.BidID)
- }
-
- if imp.Instl != legacy.Instl {
- t.Errorf("imp[%d].instl did not translate. OpenRTB %d, legacy %d", index, imp.Instl, legacy.Instl)
- }
-
- if params, _, _, _ := jsonparser.Get(imp.Ext, "bidder"); !jsonpatch.Equal(params, legacy.Params) {
- t.Errorf("imp[%d].ext.bidder did not translate. OpenRTB %s, legacy %s", index, string(params), string(legacy.Params))
- }
-
- if imp.Banner != nil {
- if imp.Banner.TopFrame != legacy.TopFrame {
- t.Errorf("imp[%d].topframe did not translate. OpenRTB %d, legacy %d", index, imp.Banner.TopFrame, legacy.TopFrame)
- }
- if imp.Banner.Format[0].W != legacy.Sizes[0].W {
- t.Errorf("imp[%d].format[0].w did not translate. OpenRTB %d, legacy %d", index, imp.Banner.Format[0].W, legacy.Sizes[0].W)
- }
- if imp.Banner.Format[0].H != legacy.Sizes[0].H {
- t.Errorf("imp[%d].format[0].h did not translate. OpenRTB %d, legacy %d", index, imp.Banner.Format[0].H, legacy.Sizes[0].H)
- }
- }
-
- if imp.Video != nil {
- if !reflect.DeepEqual(imp.Video.MIMEs, legacy.Video.Mimes) {
- t.Errorf("imp[%d].video.mimes did not translate. OpenRTB %v, legacy %v", index, imp.Video.MIMEs, legacy.Video.Mimes)
- }
- if len(imp.Video.Protocols) != len(imp.Video.Protocols) {
- t.Errorf("len(imp[%d].video.protocols) did not match. OpenRTB %d, legacy %d", index, len(imp.Video.Protocols), len(imp.Video.Protocols))
- return
- }
- for i := 0; i < len(imp.Video.Protocols); i++ {
- if int8(imp.Video.Protocols[i]) != legacy.Video.Protocols[i] {
- t.Errorf("imp[%d].video.protocol[%d] did not match. OpenRTB %d, legacy %d", index, i, imp.Video.Protocols[i], imp.Video.Protocols[i])
- }
- }
- if len(imp.Video.PlaybackMethod) > 0 {
- if int8(imp.Video.PlaybackMethod[0]) != legacy.Video.PlaybackMethod {
- t.Errorf("imp[%d].video.playbackmethod[0] did not translate. OpenRTB %d, legacy %d", index, int8(imp.Video.PlaybackMethod[0]), legacy.Video.PlaybackMethod)
- }
- }
- if imp.Video.Skip == nil {
- if legacy.Video.Skippable != 0 {
- t.Errorf("imp[%d].video.skip did not translate. OpenRTB nil, legacy %d", index, legacy.Video.Skippable)
- }
- } else {
- if int(*imp.Video.Skip) != legacy.Video.Skippable {
- t.Errorf("imp[%d].video.skip did not translate. OpenRTB %d, legacy %d", index, *imp.Video.Skip, legacy.Video.Skippable)
- }
- }
- if imp.Video.StartDelay == nil {
- if legacy.Video.Startdelay != 0 {
- t.Errorf("imp[%d].video.startdelay did not translate. OpenRTB nil, legacy %d", index, legacy.Video.Startdelay)
- }
- } else {
- if int64(*imp.Video.StartDelay) != legacy.Video.Startdelay {
- t.Errorf("imp[%d].video.startdelay did not translate. OpenRTB %d, legacy %d", index, int64(*imp.Video.StartDelay), legacy.Video.Startdelay)
- }
- }
- if imp.Video.MaxDuration != legacy.Video.Maxduration {
- t.Errorf("imp[%d].video.maxduration did not translate. OpenRTB %d, legacy %d", index, imp.Video.MaxDuration, legacy.Video.Maxduration)
- }
- if imp.Video.MinDuration != legacy.Video.Minduration {
- t.Errorf("imp[%d].video.minduration did not translate. OpenRTB %d, legacy %d", index, imp.Video.MinDuration, legacy.Video.Minduration)
- }
- }
-}
-
-type mockLegacyAdapter struct {
- returnedBids pbs.PBSBidSlice
- returnedError error
- gotRequest *pbs.PBSRequest
- gotBidder *pbs.PBSBidder
-}
-
-func (a *mockLegacyAdapter) Name() string {
- return "someFamily"
-}
-
-func (a *mockLegacyAdapter) SkipNoCookies() bool {
- return false
-}
-
-func (a *mockLegacyAdapter) GetUsersyncInfo() (*usersync.UsersyncInfo, error) {
- return nil, nil
-}
-
-func (a *mockLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) {
- a.gotRequest = req
- a.gotBidder = bidder
- return a.returnedBids, a.returnedError
-}
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index aa07ed0c77b..f38a6c0266c 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -87,15 +87,15 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
}
ex := &exchange{
- adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()),
- me: &metricsConf.DummyMetricsEngine{},
- cache: &wellBehavedCache{},
- cacheTime: time.Duration(0),
- gDPR: gdpr.AlwaysAllow{},
- currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
- UsersyncIfAmbiguous: false,
- categoriesFetcher: categoriesFetcher,
- bidIDGenerator: &mockBidIDGenerator{false, false},
+ adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()),
+ me: &metricsConf.DummyMetricsEngine{},
+ cache: &wellBehavedCache{},
+ cacheTime: time.Duration(0),
+ gDPR: gdpr.AlwaysAllow{},
+ currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
+ gdprDefaultValue: gdpr.SignalYes,
+ categoriesFetcher: categoriesFetcher,
+ bidIDGenerator: &mockBidIDGenerator{false, false},
}
imps := buildImps(t, mockBids)
diff --git a/exchange/utils.go b/exchange/utils.go
index 38d21751f8f..258d52d9055 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -28,18 +28,16 @@ var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{
const unknownBidder string = ""
-func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) {
+func BidderToPrebidSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) {
bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain)
- if req != nil {
- for _, schainWrapper := range req.Prebid.SChains {
- for _, bidder := range schainWrapper.Bidders {
- if _, present := bidderToSChains[bidder]; present {
- return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+
- "it must contain no more than one per bidder.", bidder)
- } else {
- bidderToSChains[bidder] = &schainWrapper.SChain
- }
+ for _, schainWrapper := range sChains {
+ for _, bidder := range schainWrapper.Bidders {
+ if _, present := bidderToSChains[bidder]; present {
+ return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+
+ "it must contain no more than one per bidder.", bidder)
+ } else {
+ bidderToSChains[bidder] = &schainWrapper.SChain
}
}
}
@@ -56,9 +54,10 @@ func cleanOpenRTBRequests(ctx context.Context,
req AuctionRequest,
requestExt *openrtb_ext.ExtRequest,
gDPR gdpr.Permissions,
- usersyncIfAmbiguous bool,
+ metricsEngine metrics.MetricsEngine,
+ gdprDefaultValue gdpr.Signal,
privacyConfig config.Privacy,
- account *config.Account) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) {
+ account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) {
impsByBidder, err := splitImps(req.BidRequest.Imp)
if err != nil {
@@ -71,9 +70,10 @@ func cleanOpenRTBRequests(ctx context.Context,
return
}
- bidderRequests, errs = getAuctionBidderRequests(req, requestExt, impsByBidder, aliases)
+ var allBidderRequests []BidderRequest
+ allBidderRequests, errs = getAuctionBidderRequests(req, requestExt, impsByBidder, aliases)
- if len(bidderRequests) == 0 {
+ if len(allBidderRequests) == 0 {
return
}
@@ -85,7 +85,7 @@ func cleanOpenRTBRequests(ctx context.Context,
if err != nil {
errs = append(errs, err)
}
- gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && !usersyncIfAmbiguous)
+ gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == gdpr.SignalYes)
ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &req.Account, aliases, integrationTypeMap[req.LegacyLabels.RType])
if err != nil {
@@ -117,7 +117,10 @@ func cleanOpenRTBRequests(ctx context.Context,
}
// bidder level privacy policies
- for _, bidderRequest := range bidderRequests {
+ allowedBidderRequests = make([]BidderRequest, 0, len(allBidderRequests))
+ for _, bidderRequest := range allBidderRequests {
+ bidRequestAllowed := true
+
// CCPA
privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String())
@@ -133,7 +136,9 @@ func cleanOpenRTBRequests(ctx context.Context,
}
}
var publisherID = req.LegacyLabels.PubID
- _, geo, id, err := gDPR.PersonalInfoAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent, weakVendorEnforcement)
+ bidReq, geo, id, err := gDPR.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent, weakVendorEnforcement)
+ bidRequestAllowed = bidReq
+
if err == nil {
privacyEnforcement.GDPRGeo = !geo
privacyEnforcement.GDPRID = !id
@@ -141,9 +146,16 @@ func cleanOpenRTBRequests(ctx context.Context,
privacyEnforcement.GDPRGeo = true
privacyEnforcement.GDPRID = true
}
+
+ if !bidRequestAllowed {
+ metricsEngine.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName)
+ }
}
- privacyEnforcement.Apply(bidderRequest.BidRequest)
+ if bidRequestAllowed {
+ privacyEnforcement.Apply(bidderRequest.BidRequest)
+ allowedBidderRequests = append(allowedBidderRequests, bidderRequest)
+ }
}
return
@@ -164,7 +176,8 @@ func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestT
}
func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) {
- ccpaPolicy, err := ccpa.ReadFromRequest(orig)
+ // Quick extra wrapper until RequestWrapper makes its way into CleanRequests
+ ccpaPolicy, err := ccpa.ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: orig})
if err != nil {
return privacy.NilPolicyEnforcer{}, err
}
@@ -234,9 +247,12 @@ func getAuctionBidderRequests(req AuctionRequest,
var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain
- sChainsByBidder, err = BidderToPrebidSChains(requestExt)
- if err != nil {
- return nil, []error{err}
+ // Quick extra wrapper until RequestWrapper makes its way into CleanRequests
+ if requestExt != nil {
+ sChainsByBidder, err = BidderToPrebidSChains(requestExt.Prebid.SChains)
+ if err != nil {
+ return nil, []error{err}
+ }
}
reqExt, err := getExtJson(req.BidRequest, requestExt)
@@ -364,7 +380,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) {
userExt.Prebid = nil
// Remarshal (instead of removing) if the ext has other known fields
- if userExt.Consent != "" || userExt.DigiTrust != nil || len(userExt.Eids) > 0 {
+ if userExt.Consent != "" || len(userExt.Eids) > 0 {
if newUserExtBytes, err := json.Marshal(userExt); err != nil {
return nil, err
} else {
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 55a0950aac6..1d13928b59c 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -14,14 +14,16 @@ import (
"github.com/prebid/prebid-server/metrics"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
)
// permissionsMock mocks the Permissions interface for tests
-//
-// It only allows appnexus for GDPR consent
type permissionsMock struct {
- personalInfoAllowed bool
- personalInfoAllowedError error
+ allowAllBidders bool
+ allowedBidders []openrtb_ext.BidderName
+ passGeo bool
+ passID bool
+ activitiesError error
}
func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, gdpr gdpr.Signal, consent string) (bool, error) {
@@ -32,8 +34,18 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
return true, nil
}
-func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) {
- return p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowedError
+func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) {
+ if p.allowAllBidders {
+ return true, p.passGeo, p.passID, p.activitiesError
+ }
+
+ for _, allowedBidder := range p.allowedBidders {
+ if bidder == allowedBidder {
+ allowBidRequest = true
+ }
+ }
+
+ return allowBidRequest, p.passGeo, p.passID, p.activitiesError
}
func assertReq(t *testing.T, bidderRequests []BidderRequest,
@@ -465,7 +477,9 @@ func TestCleanOpenRTBRequests(t *testing.T) {
}
for _, test := range testCases {
- bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil)
+ metricsMock := metrics.MetricsEngineMock{}
+ permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
+ bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, gdpr.SignalNo, privacyConfig, nil)
if test.hasError {
assert.NotNil(t, err, "Error shouldn't be nil")
} else {
@@ -620,8 +634,9 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
context.Background(),
auctionReq,
nil,
- &permissionsMock{personalInfoAllowed: true},
- true,
+ &permissionsMock{allowAllBidders: true, passGeo: true, passID: true},
+ &metrics.MetricsEngineMock{},
+ gdpr.SignalNo,
privacyConfig,
nil)
result := bidderRequests[0]
@@ -681,7 +696,9 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) {
Enforce: true,
},
}
- _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil)
+ permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
+ metrics := metrics.MetricsEngineMock{}
+ _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil)
assert.ElementsMatch(t, []error{test.expectError}, errs, test.description)
}
@@ -721,7 +738,9 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) {
UserSyncs: &emptyUsersync{},
}
- bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}, nil)
+ permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
+ metrics := metrics.MetricsEngineMock{}
+ bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
result := bidderRequests[0]
assert.Nil(t, errs)
@@ -828,7 +847,9 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) {
UserSyncs: &emptyUsersync{},
}
- bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissionsMock{}, true, config.Privacy{}, nil)
+ permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
+ metrics := metrics.MetricsEngineMock{}
+ bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
if test.hasError == true {
assert.NotNil(t, errs)
assert.Len(t, bidderRequests, 0)
@@ -1409,7 +1430,9 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
},
}
- results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil)
+ permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
+ metrics := metrics.MetricsEngineMock{}
+ results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil)
result := results[0]
assert.Nil(t, errs)
@@ -1425,7 +1448,6 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
}
func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
- tcf1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY"
tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA"
trueValue, falseValue := true, false
@@ -1437,7 +1459,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
gdprConsent string
gdprScrub bool
permissionsError error
- userSyncIfAmbiguous bool
+ gdprDefaultValue string
expectPrivacyLabels metrics.PrivacyLabels
expectError bool
}{
@@ -1448,129 +1470,125 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
gdpr: "1",
gdprConsent: "malformed",
gdprScrub: false,
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: true,
GDPRTCFVersion: "",
},
},
{
- description: "Enforce - TCF 1",
- gdprAccountEnabled: &trueValue,
- gdprHostEnabled: true,
- gdpr: "1",
- gdprConsent: tcf1Consent,
- gdprScrub: true,
- expectPrivacyLabels: metrics.PrivacyLabels{
- GDPREnforced: true,
- GDPRTCFVersion: metrics.TCFVersionV1,
- },
- },
- {
- description: "Enforce - TCF 2",
+ description: "Enforce",
gdprAccountEnabled: &trueValue,
gdprHostEnabled: true,
gdpr: "1",
gdprConsent: tcf2Consent,
gdprScrub: true,
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: true,
GDPRTCFVersion: metrics.TCFVersionV2,
},
},
{
- description: "Not Enforce - TCF 1",
+ description: "Not Enforce",
gdprAccountEnabled: &trueValue,
gdprHostEnabled: true,
gdpr: "0",
- gdprConsent: tcf1Consent,
+ gdprConsent: tcf2Consent,
gdprScrub: false,
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: false,
GDPRTCFVersion: "",
},
},
{
- description: "Enforce - TCF 1; GDPR signal extraction error",
+ description: "Enforce; GDPR signal extraction error",
gdprAccountEnabled: &trueValue,
gdprHostEnabled: true,
gdpr: "0{",
- gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY",
+ gdprConsent: tcf2Consent,
gdprScrub: true,
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: true,
- GDPRTCFVersion: metrics.TCFVersionV1,
+ GDPRTCFVersion: metrics.TCFVersionV2,
},
expectError: true,
},
{
- description: "Enforce - TCF 1; account GDPR enabled, host GDPR setting disregarded",
+ description: "Enforce; account GDPR enabled, host GDPR setting disregarded",
gdprAccountEnabled: &trueValue,
gdprHostEnabled: false,
gdpr: "1",
- gdprConsent: tcf1Consent,
+ gdprConsent: tcf2Consent,
gdprScrub: true,
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: true,
- GDPRTCFVersion: metrics.TCFVersionV1,
+ GDPRTCFVersion: metrics.TCFVersionV2,
},
},
{
- description: "Not Enforce - TCF 1; account GDPR disabled, host GDPR setting disregarded",
+ description: "Not Enforce; account GDPR disabled, host GDPR setting disregarded",
gdprAccountEnabled: &falseValue,
gdprHostEnabled: true,
gdpr: "1",
- gdprConsent: tcf1Consent,
+ gdprConsent: tcf2Consent,
gdprScrub: false,
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: false,
GDPRTCFVersion: "",
},
},
{
- description: "Enforce - TCF 1; account GDPR not specified, host GDPR enabled",
+ description: "Enforce; account GDPR not specified, host GDPR enabled",
gdprAccountEnabled: nil,
gdprHostEnabled: true,
gdpr: "1",
- gdprConsent: tcf1Consent,
+ gdprConsent: tcf2Consent,
gdprScrub: true,
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: true,
- GDPRTCFVersion: metrics.TCFVersionV1,
+ GDPRTCFVersion: metrics.TCFVersionV2,
},
},
{
- description: "Not Enforce - TCF 1; account GDPR not specified, host GDPR disabled",
+ description: "Not Enforce; account GDPR not specified, host GDPR disabled",
gdprAccountEnabled: nil,
gdprHostEnabled: false,
gdpr: "1",
- gdprConsent: tcf1Consent,
+ gdprConsent: tcf2Consent,
gdprScrub: false,
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: false,
GDPRTCFVersion: "",
},
},
{
- description: "Enforce - Ambiguous signal, don't sync user if ambiguous",
- gdprAccountEnabled: nil,
- gdprHostEnabled: true,
- gdpr: "null",
- gdprConsent: tcf1Consent,
- gdprScrub: true,
- userSyncIfAmbiguous: false,
+ description: "Enforce - Ambiguous signal, don't sync user if ambiguous",
+ gdprAccountEnabled: nil,
+ gdprHostEnabled: true,
+ gdpr: "null",
+ gdprConsent: tcf2Consent,
+ gdprScrub: true,
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: true,
- GDPRTCFVersion: metrics.TCFVersionV1,
+ GDPRTCFVersion: metrics.TCFVersionV2,
},
},
{
- description: "Not Enforce - Ambiguous signal, sync user if ambiguous",
- gdprAccountEnabled: nil,
- gdprHostEnabled: true,
- gdpr: "null",
- gdprConsent: tcf1Consent,
- gdprScrub: false,
- userSyncIfAmbiguous: true,
+ description: "Not Enforce - Ambiguous signal, sync user if ambiguous",
+ gdprAccountEnabled: nil,
+ gdprHostEnabled: true,
+ gdpr: "null",
+ gdprConsent: tcf2Consent,
+ gdprScrub: false,
+ gdprDefaultValue: "0",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: false,
GDPRTCFVersion: "",
@@ -1581,12 +1599,13 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
gdprAccountEnabled: nil,
gdprHostEnabled: true,
gdpr: "1",
- gdprConsent: tcf1Consent,
+ gdprConsent: tcf2Consent,
gdprScrub: true,
permissionsError: errors.New("Some error"),
+ gdprDefaultValue: "1",
expectPrivacyLabels: metrics.PrivacyLabels{
GDPREnforced: true,
- GDPRTCFVersion: metrics.TCFVersionV1,
+ GDPRTCFVersion: metrics.TCFVersionV2,
},
},
}
@@ -1600,8 +1619,8 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
privacyConfig := config.Privacy{
GDPR: config.GDPR{
- Enabled: test.gdprHostEnabled,
- UsersyncIfAmbiguous: test.userSyncIfAmbiguous,
+ Enabled: test.gdprHostEnabled,
+ DefaultValue: test.gdprDefaultValue,
TCF2: config.TCF2{
Enabled: true,
},
@@ -1620,12 +1639,18 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
Account: accountConfig,
}
+ gdprDefaultValue := gdpr.SignalYes
+ if test.gdprDefaultValue == "0" {
+ gdprDefaultValue = gdpr.SignalNo
+ }
+
results, privacyLabels, errs := cleanOpenRTBRequests(
context.Background(),
auctionReq,
nil,
- &permissionsMock{personalInfoAllowed: !test.gdprScrub, personalInfoAllowedError: test.permissionsError},
- test.userSyncIfAmbiguous,
+ &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError},
+ &metrics.MetricsEngineMock{},
+ gdprDefaultValue,
privacyConfig,
nil)
result := results[0]
@@ -1647,6 +1672,97 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
}
}
+func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) {
+ testCases := []struct {
+ description string
+ gdprEnforced bool
+ gdprAllowedBidders []openrtb_ext.BidderName
+ expectedBidders []openrtb_ext.BidderName
+ expectedBlockedBidders []openrtb_ext.BidderName
+ }{
+ {
+ description: "gdpr enforced, one request allowed and one request blocked",
+ gdprEnforced: true,
+ gdprAllowedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus},
+ expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus},
+ expectedBlockedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderRubicon},
+ },
+ {
+ description: "gdpr enforced, two requests allowed and no requests blocked",
+ gdprEnforced: true,
+ gdprAllowedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon},
+ expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon},
+ expectedBlockedBidders: []openrtb_ext.BidderName{},
+ },
+ {
+ description: "gdpr not enforced, two requests allowed and no requests blocked",
+ gdprEnforced: false,
+ gdprAllowedBidders: []openrtb_ext.BidderName{},
+ expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon},
+ expectedBlockedBidders: []openrtb_ext.BidderName{},
+ },
+ }
+
+ for _, test := range testCases {
+ req := newBidRequest(t)
+ req.Regs = &openrtb2.Regs{
+ Ext: json.RawMessage(`{"gdpr":1}`),
+ }
+ req.Imp[0].Ext = json.RawMessage(`{"appnexus": {"placementId": 1}, "rubicon": {}}`)
+
+ privacyConfig := config.Privacy{
+ GDPR: config.GDPR{
+ Enabled: test.gdprEnforced,
+ DefaultValue: "0",
+ TCF2: config.TCF2{
+ Enabled: true,
+ },
+ },
+ }
+
+ accountConfig := config.Account{
+ GDPR: config.AccountGDPR{
+ Enabled: nil,
+ },
+ }
+
+ auctionReq := AuctionRequest{
+ BidRequest: req,
+ UserSyncs: &emptyUsersync{},
+ Account: accountConfig,
+ }
+
+ metricsMock := metrics.MetricsEngineMock{}
+ metricsMock.Mock.On("RecordAdapterGDPRRequestBlocked", mock.Anything).Return()
+
+ results, _, errs := cleanOpenRTBRequests(
+ context.Background(),
+ auctionReq,
+ nil,
+ &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil},
+ &metricsMock,
+ gdpr.SignalNo,
+ privacyConfig,
+ nil)
+
+ // extract bidder name from each request in the results
+ bidders := []openrtb_ext.BidderName{}
+ for _, req := range results {
+ bidders = append(bidders, req.BidderName)
+ }
+
+ assert.Empty(t, errs, test.description)
+ assert.ElementsMatch(t, bidders, test.expectedBidders, test.description)
+
+ for _, blockedBidder := range test.expectedBlockedBidders {
+ metricsMock.AssertCalled(t, "RecordAdapterGDPRRequestBlocked", blockedBidder)
+ }
+ for _, allowedBidder := range test.expectedBidders {
+ metricsMock.AssertNotCalled(t, "RecordAdapterGDPRRequestBlocked", allowedBidder)
+ }
+ }
+}
+
// newAdapterAliasBidRequest builds a BidRequest with aliases
func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest {
dnt := int8(1)
@@ -1672,7 +1788,7 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest {
User: &openrtb2.User{
ID: "our-id",
BuyerUID: "their-id",
- Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`),
+ Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`),
},
Regs: &openrtb2.Regs{
Ext: json.RawMessage(`{"gdpr":1}`),
@@ -1717,7 +1833,7 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest {
ID: "our-id",
BuyerUID: "their-id",
Yob: 1982,
- Ext: json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`),
+ Ext: json.RawMessage(`{}`),
},
Imp: []openrtb2.Imp{{
ID: "some-imp-id",
@@ -1792,7 +1908,7 @@ func TestBidderToPrebidChains(t *testing.T) {
},
}
- output, err := BidderToPrebidSChains(&input)
+ output, err := BidderToPrebidSChains(input.Prebid.SChains)
assert.Nil(t, err)
assert.Equal(t, len(output), 4)
@@ -1818,7 +1934,7 @@ func TestBidderToPrebidChainsDiscardMultipleChainsForBidder(t *testing.T) {
},
}
- output, err := BidderToPrebidSChains(&input)
+ output, err := BidderToPrebidSChains(input.Prebid.SChains)
assert.NotNil(t, err)
assert.Nil(t, output)
@@ -1831,7 +1947,7 @@ func TestBidderToPrebidChainsNilSChains(t *testing.T) {
},
}
- output, err := BidderToPrebidSChains(&input)
+ output, err := BidderToPrebidSChains(input.Prebid.SChains)
assert.Nil(t, err)
assert.Equal(t, len(output), 0)
@@ -1844,7 +1960,7 @@ func TestBidderToPrebidChainsZeroLengthSChains(t *testing.T) {
},
}
- output, err := BidderToPrebidSChains(&input)
+ output, err := BidderToPrebidSChains(input.Prebid.SChains)
assert.Nil(t, err)
assert.Equal(t, len(output), 0)
diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go
index 616d0f0ae07..d2d282b2fec 100644
--- a/gdpr/gdpr.go
+++ b/gdpr/gdpr.go
@@ -5,6 +5,7 @@ import (
"net/http"
"strconv"
+ "github.com/prebid/go-gdpr/consentconstants"
"github.com/prebid/go-gdpr/vendorlist"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/errortypes"
@@ -25,12 +26,11 @@ type Permissions interface {
// Determines whether or not to send PI information to a bidder, or mask it out.
//
// If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent.
- PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error)
+ AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowBidReq bool, passGeo bool, passID bool, err error)
}
// Versions of the GDPR TCF technical specification.
const (
- tcf1SpecVersion uint8 = 1
tcf2SpecVersion uint8 = 2
)
@@ -40,12 +40,31 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_
return &AlwaysAllow{}
}
+ gdprDefaultValue := SignalYes
+ if cfg.DefaultValue == "0" {
+ gdprDefaultValue = SignalNo
+ }
+
+ purposeConfigs := map[consentconstants.Purpose]config.TCF2Purpose{
+ 1: cfg.TCF2.Purpose1,
+ 2: cfg.TCF2.Purpose2,
+ 3: cfg.TCF2.Purpose3,
+ 4: cfg.TCF2.Purpose4,
+ 5: cfg.TCF2.Purpose5,
+ 6: cfg.TCF2.Purpose6,
+ 7: cfg.TCF2.Purpose7,
+ 8: cfg.TCF2.Purpose8,
+ 9: cfg.TCF2.Purpose9,
+ 10: cfg.TCF2.Purpose10,
+ }
+
permissionsImpl := &permissionsImpl{
- cfg: cfg,
- vendorIDs: vendorIDs,
+ cfg: cfg,
+ gdprDefaultValue: gdprDefaultValue,
+ purposeConfigs: purposeConfigs,
+ vendorIDs: vendorIDs,
fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: newVendorListFetcherTCF1(cfg),
- tcf2SpecVersion: newVendorListFetcherTCF2(ctx, cfg, client, vendorListURLMaker)},
+ tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)},
}
if cfg.HostVendorID == 0 {
diff --git a/gdpr/impl.go b/gdpr/impl.go
index 55d1cd4aeb0..5f7e3e73fe2 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -5,8 +5,8 @@ import (
"fmt"
"github.com/prebid/go-gdpr/api"
- tcf1constants "github.com/prebid/go-gdpr/consentconstants"
- consentconstants "github.com/prebid/go-gdpr/consentconstants/tcf2"
+ "github.com/prebid/go-gdpr/consentconstants"
+ tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2"
"github.com/prebid/go-gdpr/vendorconsent"
tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2"
"github.com/prebid/go-gdpr/vendorlist"
@@ -28,9 +28,11 @@ const (
)
type permissionsImpl struct {
- cfg config.GDPR
- vendorIDs map[openrtb_ext.BidderName]uint16
- fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error)
+ cfg config.GDPR
+ gdprDefaultValue Signal
+ purposeConfigs map[consentconstants.Purpose]config.TCF2Purpose
+ vendorIDs map[openrtb_ext.BidderName]uint16
+ fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error)
}
func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) {
@@ -40,7 +42,7 @@ func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Sig
return true, nil
}
- return p.allowSync(ctx, uint16(p.cfg.HostVendorID), consent)
+ return p.allowSync(ctx, uint16(p.cfg.HostVendorID), consent, false)
}
func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) {
@@ -52,18 +54,19 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_
id, ok := p.vendorIDs[bidder]
if ok {
- return p.allowSync(ctx, id, consent)
+ vendorException := p.isVendorException(consentconstants.Purpose(1), bidder)
+ return p.allowSync(ctx, id, consent, vendorException)
}
return false, nil
}
-func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context,
+func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context,
bidder openrtb_ext.BidderName,
PublisherID string,
gdprSignal Signal,
consent string,
- weakVendorEnforcement bool) (allowPI bool, allowGeo bool, allowID bool, err error) {
+ weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) {
if _, ok := p.cfg.NonStandardPublisherMap[PublisherID]; ok {
return true, true, true, nil
}
@@ -79,13 +82,15 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context,
}
if id, ok := p.vendorIDs[bidder]; ok {
- return p.allowPI(ctx, id, consent, weakVendorEnforcement)
+ return p.allowActivities(ctx, id, bidder, consent, weakVendorEnforcement)
+ } else if weakVendorEnforcement {
+ return p.allowActivities(ctx, 0, bidder, consent, weakVendorEnforcement)
}
return p.defaultVendorPermissions()
}
-func (p *permissionsImpl) defaultVendorPermissions() (allowPI bool, allowGeo bool, allowID bool, err error) {
+func (p *permissionsImpl) defaultVendorPermissions() (allowBidRequest bool, passGeo bool, passID bool, err error) {
return false, false, false, nil
}
@@ -94,14 +99,14 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal {
return gdprSignal
}
- if p.cfg.UsersyncIfAmbiguous {
+ if p.gdprDefaultValue == SignalNo {
return SignalNo
}
return SignalYes
}
-func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string) (bool, error) {
+func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string, vendorException bool) (bool, error) {
if consent == "" {
return false, nil
@@ -116,81 +121,75 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen
return false, nil
}
- // InfoStorageAccess is the same across TCF 1 and TCF 2
- if parsedConsent.Version() == 2 {
- if !p.cfg.TCF2.Purpose1.Enabled {
- // We are not enforcing purpose 1
- return true, nil
- }
- consent, ok := parsedConsent.(tcf2.ConsentMetadata)
- if !ok {
- err := fmt.Errorf("Unable to access TCF2 parsed consent")
- return false, err
- }
- return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, false), nil
- }
- if vendor.Purpose(consentconstants.InfoStorageAccess) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && parsedConsent.VendorConsent(vendorID) {
+ if !p.cfg.TCF2.Purpose1.Enabled {
return true, nil
}
- return false, nil
+ consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata)
+ if !ok {
+ err := fmt.Errorf("Unable to access TCF2 parsed consent")
+ return false, err
+ }
+ return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, vendorException, false), nil
}
-func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) {
+func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, bidder openrtb_ext.BidderName, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) {
parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent)
if err != nil {
return false, false, false, err
}
+ // vendor will be nil if not a valid TCF2 consent string
if vendor == nil {
- return false, false, false, nil
+ if weakVendorEnforcement && parsedConsent.Version() == 2 {
+ vendor = vendorTrue{}
+ } else {
+ return false, false, false, nil
+ }
}
- if parsedConsent.Version() == 2 {
- if p.cfg.TCF2.Enabled {
- return p.allowPITCF2(parsedConsent, vendor, vendorID, weakVendorEnforcement)
- }
- if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && (parsedConsent.VendorConsent(vendorID) || weakVendorEnforcement) {
- return true, true, true, nil
- }
- } else {
- if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) {
- return true, true, true, nil
- }
+ if !p.cfg.TCF2.Enabled {
+ return true, false, false, nil
}
- return false, false, false, nil
-}
-func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16, weakVendorEnforcement bool) (allowPI bool, allowGeo bool, allowID bool, err error) {
- consent, ok := parsedConsent.(tcf2.ConsentMetadata)
- err = nil
- allowPI = false
- allowGeo = false
- allowID = false
+ consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata)
if !ok {
err = fmt.Errorf("Unable to access TCF2 parsed consent")
return
}
+
if p.cfg.TCF2.SpecialPurpose1.Enabled {
- allowGeo = consent.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement)
+ vendorException := p.isSpecialPurposeVendorException(bidder)
+ passGeo = vendorException || (consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement))
} else {
- allowGeo = true
+ passGeo = true
+ }
+ if p.cfg.TCF2.Purpose2.Enabled {
+ vendorException := p.isVendorException(consentconstants.Purpose(2), bidder)
+ allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), vendorException, weakVendorEnforcement)
+ } else {
+ allowBidRequest = true
}
for i := 2; i <= 10; i++ {
- if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i), weakVendorEnforcement) {
- allowID = true
+ vendorException := p.isVendorException(consentconstants.Purpose(i), bidder)
+ if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), vendorException, weakVendorEnforcement) {
+ passID = true
break
}
}
- // Set to true so any purpose check can flip it to false
- allowPI = true
- if p.cfg.TCF2.Purpose1.Enabled {
- allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, weakVendorEnforcement)
- }
- if p.cfg.TCF2.Purpose2.Enabled {
- allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.BasicAdserving, weakVendorEnforcement)
+
+ return
+}
+
+func (p *permissionsImpl) isVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (vendorException bool) {
+ if _, ok := p.purposeConfigs[purpose].VendorExceptionMap[bidder]; ok {
+ vendorException = true
}
- if p.cfg.TCF2.Purpose7.Enabled {
- allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.AdPerformance, weakVendorEnforcement)
+ return
+}
+
+func (p *permissionsImpl) isSpecialPurposeVendorException(bidder openrtb_ext.BidderName) (vendorException bool) {
+ if _, ok := p.cfg.TCF2.SpecialPurpose1.VendorExceptionMap[bidder]; ok {
+ vendorException = true
}
return
}
@@ -199,16 +198,20 @@ const pubRestrictNotAllowed = 0
const pubRestrictRequireConsent = 1
const pubRestrictRequireLegitInterest = 2
-func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose, weakVendorEnforcement bool) bool {
- if purpose == consentconstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() {
+func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, vendorException, weakVendorEnforcement bool) bool {
+ if purpose == tcf2ConsentConstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() {
return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed
}
if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) {
return false
}
- purposeAllowed := consent.PurposeAllowed(purpose) && (weakVendorEnforcement || (vendor.Purpose(purpose) && consent.VendorConsent(vendorID)))
- legitInterest := consent.PurposeLITransparency(purpose) && (weakVendorEnforcement || (vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID)))
+ if vendorException {
+ return true
+ }
+
+ purposeAllowed := p.consentEstablished(consent, vendor, vendorID, purpose, weakVendorEnforcement)
+ legitInterest := p.legitInterestEstablished(consent, vendor, vendorID, purpose, weakVendorEnforcement)
if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) {
return purposeAllowed
@@ -221,6 +224,38 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.
return purposeAllowed || legitInterest
}
+func (p *permissionsImpl) consentEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool {
+ if !consent.PurposeAllowed(purpose) {
+ return false
+ }
+ if weakVendorEnforcement {
+ return true
+ }
+ if !p.purposeConfigs[purpose].EnforceVendors {
+ return true
+ }
+ if vendor.Purpose(purpose) && consent.VendorConsent(vendorID) {
+ return true
+ }
+ return false
+}
+
+func (p *permissionsImpl) legitInterestEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool {
+ if !consent.PurposeLITransparency(purpose) {
+ return false
+ }
+ if weakVendorEnforcement {
+ return true
+ }
+ if !p.purposeConfigs[purpose].EnforceVendors {
+ return true
+ }
+ if vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID) {
+ return true
+ }
+ return false
+}
+
func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent api.VendorConsents, vendor api.Vendor, err error) {
parsedConsent, err = vendorconsent.ParseString(consent)
if err != nil {
@@ -232,9 +267,10 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons
}
version := parsedConsent.Version()
- if version < 1 || version > 2 {
+ if version != 2 {
return
}
+
vendorList, err := p.fetchVendorList[version](ctx, parsedConsent.VendorListVersion())
if err != nil {
return
@@ -265,21 +301,25 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B
return true, nil
}
-func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) {
+func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) {
return true, true, true, nil
}
-// Exporting to allow for easy test setups
-type AlwaysFail struct{}
+// vendorTrue claims everything.
+type vendorTrue struct{}
-func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) {
- return false, nil
+func (v vendorTrue) Purpose(purposeID consentconstants.Purpose) bool {
+ return true
}
-
-func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) {
- return false, nil
+func (v vendorTrue) PurposeStrict(purposeID consentconstants.Purpose) bool {
+ return true
}
-
-func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) {
- return false, false, false, nil
+func (v vendorTrue) LegitimateInterest(purposeID consentconstants.Purpose) bool {
+ return true
+}
+func (v vendorTrue) LegitimateInterestStrict(purposeID consentconstants.Purpose) bool {
+ return true
+}
+func (v vendorTrue) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpecialPurpose bool) {
+ return true
}
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index b13d469a955..f7d90f3673b 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -9,6 +9,7 @@ import (
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/openrtb_ext"
+ "github.com/prebid/go-gdpr/consentconstants"
"github.com/prebid/go-gdpr/vendorlist"
"github.com/prebid/go-gdpr/vendorlist2"
@@ -18,12 +19,12 @@ import (
func TestDisallowOnEmptyConsent(t *testing.T) {
perms := permissionsImpl{
cfg: config.GDPR{
- HostVendorID: 3,
- UsersyncIfAmbiguous: true,
+ HostVendorID: 3,
+ DefaultValue: "0",
},
- vendorIDs: nil,
+ gdprDefaultValue: SignalNo,
+ vendorIDs: nil,
fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: failedListFetcher,
tcf2SpecVersion: failedListFetcher,
},
}
@@ -49,106 +50,128 @@ func TestAllowOnSignalNo(t *testing.T) {
}
func TestAllowedSyncs(t *testing.T) {
- vendorListData := tcf1MarshalVendorList(tcf1VendorList{
- VendorListVersion: 1,
- Vendors: []tcf1Vendor{
- {ID: 2, Purposes: []int{1}},
- {ID: 3, Purposes: []int{1}},
+ vendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABCAAIAAAAAAAAAAACEAAAAA"
+ vendorListData := MarshalVendorList(vendorList{
+ VendorListVersion: 2,
+ Vendors: map[string]*vendor{
+ "2": {
+ ID: 2,
+ Purposes: []int{1},
+ },
},
})
+
perms := permissionsImpl{
cfg: config.GDPR{
HostVendorID: 2,
+ TCF2: config.TCF2{
+ Purpose1: config.TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ },
+ },
},
vendorIDs: map[openrtb_ext.BidderName]uint16{
openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 3,
},
fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 1: parseVendorListData(t, vendorListData),
- }),
tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 1: parseVendorListData(t, vendorListData),
+ 1: parseVendorListDataV2(t, vendorListData),
}),
},
}
+ perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
+ consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1,
+ }
- allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw")
+ allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2AndPurpose1Consent)
assertNilErr(t, err)
assertBoolsEqual(t, true, allowSync)
- allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw")
+ allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2AndPurpose1Consent)
assertNilErr(t, err)
assertBoolsEqual(t, true, allowSync)
}
func TestProhibitedPurposes(t *testing.T) {
- vendorListData := tcf1MarshalVendorList(tcf1VendorList{
- VendorListVersion: 1,
- Vendors: []tcf1Vendor{
- {ID: 2, Purposes: []int{1}}, // cookie reads/writes
- {ID: 3, Purposes: []int{3}}, // ad personalization
+ vendor2NoPurpose1Consent := "CPGWkCaPGWkCaApAAAENABCAAAAAAAAAAAAAABEAAAAA"
+ vendorListData := MarshalVendorList(vendorList{
+ VendorListVersion: 2,
+ Vendors: map[string]*vendor{
+ "2": {
+ ID: 2,
+ Purposes: []int{1},
+ },
},
})
+
perms := permissionsImpl{
cfg: config.GDPR{
HostVendorID: 2,
+ TCF2: config.TCF2{
+ Purpose1: config.TCF2Purpose{
+ Enabled: true,
+ },
+ },
},
vendorIDs: map[openrtb_ext.BidderName]uint16{
openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 3,
},
fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 1: parseVendorListData(t, vendorListData),
- }),
tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 1: parseVendorListData(t, vendorListData),
+ 1: parseVendorListDataV2(t, vendorListData),
}),
},
}
- allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw")
+ allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2NoPurpose1Consent)
assertNilErr(t, err)
assertBoolsEqual(t, false, allowSync)
- allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw")
+ allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2NoPurpose1Consent)
assertNilErr(t, err)
assertBoolsEqual(t, false, allowSync)
}
func TestProhibitedVendors(t *testing.T) {
- vendorListData := tcf1MarshalVendorList(tcf1VendorList{
- VendorListVersion: 1,
- Vendors: []tcf1Vendor{
- {ID: 2, Purposes: []int{1}}, // cookie reads/writes
- {ID: 3, Purposes: []int{3}}, // ad personalization
+ purpose1NoVendorConsent := "CPGWkCaPGWkCaApAAAENABCAAIAAAAAAAAAAABAAAAAA"
+ vendorListData := MarshalVendorList(vendorList{
+ VendorListVersion: 2,
+ Vendors: map[string]*vendor{
+ "2": {
+ ID: 2,
+ Purposes: []int{1},
+ },
},
})
perms := permissionsImpl{
cfg: config.GDPR{
HostVendorID: 2,
+ TCF2: config.TCF2{
+ Purpose1: config.TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ },
+ },
},
vendorIDs: map[openrtb_ext.BidderName]uint16{
openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 3,
},
fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 1: parseVendorListData(t, vendorListData),
- }),
tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 1: parseVendorListData(t, vendorListData),
+ 1: parseVendorListDataV2(t, vendorListData),
}),
},
}
+ perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
+ consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1,
+ }
- allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA")
+ allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, purpose1NoVendorConsent)
assertNilErr(t, err)
assertBoolsEqual(t, false, allowSync)
- allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA")
+ allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, purpose1NoVendorConsent)
assertNilErr(t, err)
assertBoolsEqual(t, false, allowSync)
}
@@ -159,7 +182,6 @@ func TestMalformedConsent(t *testing.T) {
HostVendorID: 2,
},
fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: listFetcher(nil),
tcf2SpecVersion: listFetcher(nil),
},
}
@@ -169,134 +191,149 @@ func TestMalformedConsent(t *testing.T) {
assertBoolsEqual(t, false, sync)
}
-func TestAllowPersonalInfo(t *testing.T) {
+func TestAllowActivities(t *testing.T) {
bidderAllowedByConsent := openrtb_ext.BidderAppnexus
bidderBlockedByConsent := openrtb_ext.BidderRubicon
- consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA"
+ vendor2AndPurpose2Consent := "CPGWbY_PGWbY_GYAAAENABCAAEAAAAAAAAAAACEAAAAA"
tests := []struct {
description string
bidderName openrtb_ext.BidderName
publisherID string
- userSyncIfAmbiguous bool
+ gdprDefaultValue string
gdpr Signal
consent string
- allowPI bool
+ passID bool
weakVendorEnforcement bool
}{
{
- description: "Allow PI - Non standard publisher",
- bidderName: bidderBlockedByConsent,
- publisherID: "appNexusAppID",
- userSyncIfAmbiguous: false,
- gdpr: SignalYes,
- consent: consent,
- allowPI: true,
+ description: "Allow PI - Non standard publisher",
+ bidderName: bidderBlockedByConsent,
+ publisherID: "appNexusAppID",
+ gdprDefaultValue: "1",
+ gdpr: SignalYes,
+ consent: vendor2AndPurpose2Consent,
+ passID: true,
},
{
- description: "Allow PI - known vendor with No GDPR",
- bidderName: bidderBlockedByConsent,
- userSyncIfAmbiguous: false,
- gdpr: SignalNo,
- consent: consent,
- allowPI: true,
+ description: "Allow PI - known vendor with No GDPR",
+ bidderName: bidderBlockedByConsent,
+ gdprDefaultValue: "1",
+ gdpr: SignalNo,
+ consent: vendor2AndPurpose2Consent,
+ passID: true,
},
{
- description: "Allow PI - known vendor with Yes GDPR",
- bidderName: bidderAllowedByConsent,
- userSyncIfAmbiguous: false,
- gdpr: SignalYes,
- consent: consent,
- allowPI: true,
+ description: "Allow PI - known vendor with Yes GDPR",
+ bidderName: bidderAllowedByConsent,
+ gdprDefaultValue: "1",
+ gdpr: SignalYes,
+ consent: vendor2AndPurpose2Consent,
+ passID: true,
},
{
- description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and empty consent",
- bidderName: bidderAllowedByConsent,
- userSyncIfAmbiguous: true,
- gdpr: SignalAmbiguous,
- consent: "",
- allowPI: true,
+ description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and empty consent",
+ bidderName: bidderAllowedByConsent,
+ gdprDefaultValue: "0",
+ gdpr: SignalAmbiguous,
+ consent: "",
+ passID: true,
},
{
- description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and non-empty consent",
- bidderName: bidderAllowedByConsent,
- userSyncIfAmbiguous: true,
- gdpr: SignalAmbiguous,
- consent: consent,
- allowPI: true,
+ description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and non-empty consent",
+ bidderName: bidderAllowedByConsent,
+ gdprDefaultValue: "0",
+ gdpr: SignalAmbiguous,
+ consent: vendor2AndPurpose2Consent,
+ passID: true,
},
{
- description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and empty consent",
- bidderName: bidderAllowedByConsent,
- userSyncIfAmbiguous: false,
- gdpr: SignalAmbiguous,
- consent: "",
- allowPI: false,
+ description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and empty consent",
+ bidderName: bidderAllowedByConsent,
+ gdprDefaultValue: "1",
+ gdpr: SignalAmbiguous,
+ consent: "",
+ passID: false,
},
{
- description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and non-empty consent",
- bidderName: bidderAllowedByConsent,
- userSyncIfAmbiguous: false,
- gdpr: SignalAmbiguous,
- consent: consent,
- allowPI: true,
+ description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and non-empty consent",
+ bidderName: bidderAllowedByConsent,
+ gdprDefaultValue: "1",
+ gdpr: SignalAmbiguous,
+ consent: vendor2AndPurpose2Consent,
+ passID: true,
},
{
- description: "Don't allow PI - known vendor with Yes GDPR and empty consent",
- bidderName: bidderAllowedByConsent,
- userSyncIfAmbiguous: false,
- gdpr: SignalYes,
- consent: "",
- allowPI: false,
+ description: "Don't allow PI - known vendor with Yes GDPR and empty consent",
+ bidderName: bidderAllowedByConsent,
+ gdprDefaultValue: "1",
+ gdpr: SignalYes,
+ consent: "",
+ passID: false,
},
{
- description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent",
- bidderName: bidderBlockedByConsent,
- userSyncIfAmbiguous: false,
- gdpr: SignalYes,
- consent: consent,
- allowPI: false,
+ description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent",
+ bidderName: bidderBlockedByConsent,
+ gdprDefaultValue: "1",
+ gdpr: SignalYes,
+ consent: vendor2AndPurpose2Consent,
+ passID: false,
},
}
-
- vendorListData := tcf1MarshalVendorList(tcf1VendorList{
+ vendorListData := MarshalVendorList(vendorList{
VendorListVersion: 1,
- Vendors: []tcf1Vendor{
- {ID: 2, Purposes: []int{1, 3}},
+ Vendors: map[string]*vendor{
+ "2": {
+ ID: 2,
+ Purposes: []int{2},
+ },
},
})
+
perms := permissionsImpl{
cfg: config.GDPR{
HostVendorID: 2,
NonStandardPublisherMap: map[string]struct{}{"appNexusAppID": {}},
+ TCF2: config.TCF2{
+ Enabled: true,
+ Purpose2: config.TCF2Purpose{
+ Enabled: true,
+ EnforceVendors: true,
+ },
+ },
},
vendorIDs: map[openrtb_ext.BidderName]uint16{
openrtb_ext.BidderAppnexus: 2,
},
fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 1: parseVendorListData(t, vendorListData),
- }),
tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 1: parseVendorListData(t, vendorListData),
+ 1: parseVendorListDataV2(t, vendorListData),
}),
},
}
+ perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
+ consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2,
+ }
for _, tt := range tests {
- perms.cfg.UsersyncIfAmbiguous = tt.userSyncIfAmbiguous
+ perms.cfg.DefaultValue = tt.gdprDefaultValue
+ if tt.gdprDefaultValue == "0" {
+ perms.gdprDefaultValue = SignalNo
+ } else {
+ perms.gdprDefaultValue = SignalYes
+ }
- allowPI, _, _, err := perms.PersonalInfoAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement)
+ _, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement)
assert.Nil(t, err, tt.description)
- assert.Equal(t, tt.allowPI, allowPI, tt.description)
+ assert.Equal(t, tt.passID, passID, tt.description)
}
}
-func buildTCF2VendorList34() tcf2VendorList {
- return tcf2VendorList{
+func buildVendorList34() vendorList {
+ return vendorList{
VendorListVersion: 2,
- Vendors: map[string]*tcf2Vendor{
+ Vendors: map[string]*vendor{
"2": {
ID: 2,
Purposes: []int{1},
@@ -332,81 +369,112 @@ func buildTCF2VendorList34() tcf2VendorList {
}
}
-var tcf2Config = config.GDPR{
- HostVendorID: 2,
- TCF2: config.TCF2{
- Enabled: true,
- Purpose1: config.PurposeDetail{Enabled: true},
- Purpose2: config.PurposeDetail{Enabled: true},
- Purpose7: config.PurposeDetail{Enabled: true},
- SpecialPurpose1: config.PurposeDetail{Enabled: true},
- },
+func allPurposesEnabledPermissions() (perms permissionsImpl) {
+ perms = permissionsImpl{
+ cfg: config.GDPR{
+ HostVendorID: 2,
+ TCF2: config.TCF2{
+ Enabled: true,
+ Purpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ Purpose2: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ Purpose3: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ Purpose4: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ Purpose5: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ Purpose6: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ Purpose7: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ Purpose8: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ Purpose9: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ Purpose10: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ SpecialPurpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true},
+ },
+ },
+ }
+ perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
+ consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1,
+ consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2,
+ consentconstants.Purpose(3): perms.cfg.TCF2.Purpose3,
+ consentconstants.Purpose(4): perms.cfg.TCF2.Purpose4,
+ consentconstants.Purpose(5): perms.cfg.TCF2.Purpose5,
+ consentconstants.Purpose(6): perms.cfg.TCF2.Purpose6,
+ consentconstants.Purpose(7): perms.cfg.TCF2.Purpose7,
+ consentconstants.Purpose(8): perms.cfg.TCF2.Purpose8,
+ consentconstants.Purpose(9): perms.cfg.TCF2.Purpose9,
+ consentconstants.Purpose(10): perms.cfg.TCF2.Purpose10,
+ }
+ return
}
-type tcf2TestDef struct {
+type testDef struct {
description string
bidder openrtb_ext.BidderName
consent string
- allowPI bool
- allowGeo bool
- allowID bool
+ allowBid bool
+ passGeo bool
+ passID bool
weakVendorEnforcement bool
}
-func TestAllowPersonalInfoTCF2(t *testing.T) {
- vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34())
- perms := permissionsImpl{
- cfg: tcf2Config,
- vendorIDs: map[openrtb_ext.BidderName]uint16{
- openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 6,
- openrtb_ext.BidderRubicon: 8,
- openrtb_ext.BidderOpenx: 20,
- },
- fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: nil,
- tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 34: parseVendorListDataV2(t, vendorListData),
- 74: parseVendorListDataV2(t, vendorListData),
- }),
- },
+func TestAllowActivitiesGeoAndID(t *testing.T) {
+ vendorListData := MarshalVendorList(buildVendorList34())
+
+ perms := allPurposesEnabledPermissions()
+ perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderAppnexus: 2,
+ openrtb_ext.BidderPubmatic: 6,
+ openrtb_ext.BidderRubicon: 8,
+ openrtb_ext.BidderOpenx: 20,
+ openrtb_ext.BidderAudienceNetwork: 55,
+ }
+ perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+ 34: parseVendorListDataV2(t, vendorListData),
+ 74: parseVendorListDataV2(t, vendorListData),
+ }),
}
- // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8
- // PI needs all purposes to succeed
- testDefs := []tcf2TestDef{
+ // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes and vendors 2, 6, 8
+ testDefs := []testDef{
{
description: "Appnexus vendor test, insufficient purposes claimed",
bidder: openrtb_ext.BidderAppnexus,
consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
- allowPI: false,
- allowGeo: false,
- allowID: false,
+ allowBid: false,
+ passGeo: false,
+ passID: false,
},
{
description: "Appnexus vendor test, insufficient purposes claimed, basic enforcement",
bidder: openrtb_ext.BidderAppnexus,
consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
- allowPI: true,
- allowGeo: true,
- allowID: true,
+ allowBid: true,
+ passGeo: true,
+ passID: true,
+ weakVendorEnforcement: true,
+ },
+ {
+ description: "Unknown vendor test, insufficient purposes claimed, basic enforcement",
+ bidder: openrtb_ext.BidderAudienceNetwork,
+ consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
+ allowBid: true,
+ passGeo: true,
+ passID: true,
weakVendorEnforcement: true,
},
{
description: "Pubmatic vendor test, flex purposes claimed",
bidder: openrtb_ext.BidderPubmatic,
consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
- allowPI: true,
- allowGeo: true,
- allowID: true,
+ allowBid: true,
+ passGeo: true,
+ passID: true,
},
{
description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
bidder: openrtb_ext.BidderRubicon,
consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
- allowPI: true,
- allowGeo: false,
- allowID: true,
+ allowBid: true,
+ passGeo: false,
+ passID: true,
},
{
// This requires publisher restrictions on any claimed purposes, 2-10. Vendor must declare all claimed purposes
@@ -415,232 +483,109 @@ func TestAllowPersonalInfoTCF2(t *testing.T) {
description: "OpenX vendor test, Specific purposes/LIs claimed, no geo claimed, Publisher restrictions apply",
bidder: openrtb_ext.BidderOpenx,
consent: "CPAavcCPAavcCAGABCFRBKCsAP_AAH_AAAqIHFNf_X_fb3_j-_59_9t0eY1f9_7_v-0zjgeds-8Nyd_X_L8X5mM7vB36pq4KuR4Eu3LBAQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XT_ZKY79_____7__-_____7_f__-__3_vp9V---wOJAIMBAUAgAEMAAQIFCIQAAQhiQAAAABBCIBQJIAEqgAWVwEdoIEACAxAQgQAgBBQgwCAAQAAJKAgBACwQCAAiAQAAgAEAIAAEIAILACQEAAAEAJCAAiACECAgiAAg5DAgIgCCAFABAAAuJDACAMooASBAPGQGAAKAAqACGAEwALgAjgBlgDUAHZAPsA_ACMAFLAK2AbwBMQCbAFogLYAYEAw8BkQDOQGeAM-EQHwAVABWAC4AIYAZAAywBqADZAHYAPwAgABGAClgFPANYAdUA-QCGwEOgIvASIAmwBOwCkQFyAMCAYSAw8Bk4DOQGfCQAYADgBzgN_CQTgAEAALgAoACoAGQAOAAeABAACIAFQAMIAaABqADyAIYAigBMgCqAKwAWAAuABvADmAHoAQ0AiACJgEsAS4AmgBSgC3AGGAMgAZcA1ADVAGyAO8AewA-IB9gH6AQAAjABQQClgFPAL8AYoA1gBtADcAG8AOIAegA-QCGwEOgIqAReAkQBMQCZQE2AJ2AUOApEBYoC2AFyALvAYEAwYBhIDDQGHgMiAZIAycBlwDOQGfANIAadA1gDWQoAEAYQaBIACoAKwAXABDADIAGWANQAbIA7AB-AEAAIKARgApYBT4C0ALSAawA3gB1QD5AIbAQ6Ai8BIgCbAE7AKRAXIAwIBhIDDwGMAMnAZyAzwBnwcAEAA4Bv4qA2ABQAFQAQwAmABcAEcAMsAagA7AB-AEYAKXAWgBaQDeAJBATEAmwBTYC2AFyAMCAYeAyIBnIDPAGfANyHQWQAFwAUABUADIAHAAQAAiABdADAAMYAaABqADwAH0AQwBFACZAFUAVgAsABcADEAGYAN4AcwA9ACGAERAJYAmABNACjAFKALEAW4AwwBkADKAGiANQAbIA3wB3gD2gH2AfoBGACVAFBAKeAWKAtAC0gFzALyAX4AxQBuADiQHTAdQA9ACGwEOgIiAReAkEBIgCbAE7AKHAU0AqwBYsC2ALZAXAAuQBdoC7wGEgMNAYeAxIBjADHgGSAMnAZUAywBlwDOQGfANEgaQBpIDSwGnANYAbGPABAIqAb-QgZgALAAoABkAEQALgAYgBDACYAFUALgAYgAzABvAD0AI4AWIAygBqADfAHfAPsA_ACMAFBAKGAU-AtAC0gF-AMUAdQA9ACQQEiAJsAU0AsUBaMC2ALaAXAAuQBdoDDwGJAMiAZOAzkBngDPgGiANJAaWA4AlAyAAQAAsACgAGQAOAAigBgAGIAPAAiABMACqAFwAMQAZgA2gCGgEQARIAowBSgC3AGEAMoAaoA2QB3gD8AIwAU-AtAC0gGKANwAcQA6gCHQEXgJEATYAsUBbAC7QGHgMiAZOAywBnIDPAGfANIAawA4AmACARUA38pBBAAXABQAFQAMgAcABAACKAGAAYwA0ADUAHkAQwBFACYAFIAKoAWAAuABiADMAHMAQwAiABRgClAFiALcAZQA0QBqgDZAHfAPsA_ACMAFBAKGAVsAuYBeQDaAG4APQAh0BF4CRAE2AJ2AUOApoBWwCxQFsALgAXIAu0BhoDDwGMAMiAZIAycBlwDOQGeAM-gaQBpMDWANZAbGVABAA-Ab-A.YAAAAAAAAAAA",
- allowPI: true,
- allowGeo: false,
- allowID: true,
+ allowBid: true,
+ passGeo: false,
+ passID: true,
},
}
for _, td := range testDefs {
- allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement)
- assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
- assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
- assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
- assert.EqualValuesf(t, td.allowID, allowID, "AllowGeo failure on %s", td.description)
+ allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement)
+ assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
+ assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description)
+ assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description)
+ assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description)
}
}
-func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) {
- vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34())
- perms := permissionsImpl{
- cfg: tcf2Config,
- vendorIDs: map[openrtb_ext.BidderName]uint16{
- openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 6,
- openrtb_ext.BidderRubicon: 8,
- },
- fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: nil,
- tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 34: parseVendorListDataV2(t, vendorListData),
- }),
- },
+func TestAllowActivitiesWhitelist(t *testing.T) {
+ vendorListData := MarshalVendorList(buildVendorList34())
+
+ perms := allPurposesEnabledPermissions()
+ perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderAppnexus: 2,
+ openrtb_ext.BidderPubmatic: 6,
+ openrtb_ext.BidderRubicon: 8,
+ }
+ perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+ 34: parseVendorListDataV2(t, vendorListData),
+ }),
}
+
// Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array
perms.cfg.NonStandardPublisherMap = map[string]struct{}{"appNexusAppID": {}}
- allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false)
- assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed")
- assert.EqualValuesf(t, true, allowPI, "AllowPI failure")
- assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure")
- assert.EqualValuesf(t, true, allowID, "AllowID failure")
+ _, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false)
+ assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed")
+ assert.EqualValuesf(t, true, passGeo, "PassGeo failure")
+ assert.EqualValuesf(t, true, passID, "PassID failure")
}
-func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) {
- vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34())
- perms := permissionsImpl{
- cfg: tcf2Config,
- vendorIDs: map[openrtb_ext.BidderName]uint16{
- openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 32,
- openrtb_ext.BidderRubicon: 8,
- },
- fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: nil,
- tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 15: parseVendorListDataV2(t, vendorListData),
- }),
- },
+func TestAllowActivitiesPubRestrict(t *testing.T) {
+ vendorListData := MarshalVendorList(buildVendorList34())
+
+ perms := allPurposesEnabledPermissions()
+ perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderAppnexus: 2,
+ openrtb_ext.BidderPubmatic: 32,
+ openrtb_ext.BidderRubicon: 8,
+ }
+ perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+ 15: parseVendorListDataV2(t, vendorListData),
+ }),
}
// COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA - vendors 1-10 legit interest only,
// Pub restriction on purpose 7, consent only ... no allowPI will pass, no Special purpose 1 consent
- testDefs := []tcf2TestDef{
+ testDefs := []testDef{
{
description: "Appnexus vendor test, insufficient purposes claimed",
bidder: openrtb_ext.BidderAppnexus,
consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA",
- allowPI: false,
- allowGeo: false,
- allowID: false,
+ passGeo: false,
+ passID: false,
},
{
description: "Pubmatic vendor test, flex purposes claimed",
bidder: openrtb_ext.BidderPubmatic,
consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA",
- allowPI: false,
- allowGeo: false,
- allowID: false,
+ passGeo: false,
+ passID: false,
},
{
description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
bidder: openrtb_ext.BidderRubicon,
consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA",
- allowPI: false,
- allowGeo: false,
- allowID: true,
- },
- }
-
- for _, td := range testDefs {
- allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement)
- assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
- assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
- assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
- assert.EqualValuesf(t, td.allowID, allowID, "AllowPI failure on %s", td.description)
- }
-}
-
-func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) {
- vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34())
- perms := permissionsImpl{
- cfg: tcf2Config,
- vendorIDs: map[openrtb_ext.BidderName]uint16{
- openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 10,
- openrtb_ext.BidderRubicon: 8,
- },
- fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: nil,
- tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 34: parseVendorListDataV2(t, vendorListData),
- }),
- },
- }
- perms.cfg.TCF2.PurposeOneTreatment.Enabled = true
- perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = true
-
- // COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set
- testDefs := []tcf2TestDef{
- {
- description: "Appnexus vendor test, insufficient purposes claimed",
- bidder: openrtb_ext.BidderAppnexus,
- consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
- allowPI: false,
- allowGeo: false,
- allowID: false,
- },
- {
- description: "Pubmatic vendor test, flex purposes claimed",
- bidder: openrtb_ext.BidderPubmatic,
- consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
- allowPI: true,
- allowGeo: true,
- allowID: true,
- },
- {
- description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
- bidder: openrtb_ext.BidderRubicon,
- consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
- allowPI: true,
- allowGeo: false,
- allowID: true,
+ passGeo: false,
+ passID: true,
},
}
for _, td := range testDefs {
- allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement)
- assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
- assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
- assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
- assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description)
+ _, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement)
+ assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
+ assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description)
+ assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description)
}
}
-func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) {
- vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34())
- perms := permissionsImpl{
- cfg: tcf2Config,
- vendorIDs: map[openrtb_ext.BidderName]uint16{
- openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 10,
- openrtb_ext.BidderRubicon: 8,
- },
- fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: nil,
- tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 34: parseVendorListDataV2(t, vendorListData),
- }),
- },
- }
- perms.cfg.TCF2.PurposeOneTreatment.Enabled = true
- perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = false
-
- // COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set
- // Purpose one treatment will fail PI, but allow passing the IDs.
- testDefs := []tcf2TestDef{
- {
- description: "Appnexus vendor test, insufficient purposes claimed",
- bidder: openrtb_ext.BidderAppnexus,
- consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
- allowPI: false,
- allowGeo: false,
- allowID: false,
- },
- {
- description: "Pubmatic vendor test, flex purposes claimed",
- bidder: openrtb_ext.BidderPubmatic,
- consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
- allowPI: false,
- allowGeo: true,
- allowID: true,
- },
- {
- description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
- bidder: openrtb_ext.BidderRubicon,
- consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
- allowPI: false,
- allowGeo: false,
- allowID: true,
- },
- }
+func TestAllowSync(t *testing.T) {
+ vendorListData := MarshalVendorList(buildVendorList34())
- for _, td := range testDefs {
- allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement)
- assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
- assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
- assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
- assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description)
+ perms := allPurposesEnabledPermissions()
+ perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderAppnexus: 2,
+ openrtb_ext.BidderPubmatic: 6,
+ openrtb_ext.BidderRubicon: 8,
}
-}
-
-func TestAllowSyncTCF2(t *testing.T) {
- vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34())
- perms := permissionsImpl{
- cfg: tcf2Config,
- vendorIDs: map[openrtb_ext.BidderName]uint16{
- openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 6,
- openrtb_ext.BidderRubicon: 8,
- },
- fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: nil,
- tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 34: parseVendorListDataV2(t, vendorListData),
- }),
- },
+ perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+ 34: parseVendorListDataV2(t, vendorListData),
+ }),
}
- // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8
+ // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consensts to purposes and vendors 2, 6, 8
allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
assert.NoErrorf(t, err, "Error processing HostCookiesAllowed")
assert.EqualValuesf(t, true, allowSync, "HostCookiesAllowed failure")
@@ -650,27 +595,25 @@ func TestAllowSyncTCF2(t *testing.T) {
assert.EqualValuesf(t, true, allowSync, "BidderSyncAllowed failure")
}
-func TestProhibitedPurposeSyncTCF2(t *testing.T) {
- tcf2VendorList34 := buildTCF2VendorList34()
- tcf2VendorList34.Vendors["8"].Purposes = []int{7}
- vendorListData := tcf2MarshalVendorList(tcf2VendorList34)
- perms := permissionsImpl{
- cfg: tcf2Config,
- vendorIDs: map[openrtb_ext.BidderName]uint16{
- openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 6,
- openrtb_ext.BidderRubicon: 8,
- },
- fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: nil,
- tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 34: parseVendorListDataV2(t, vendorListData),
- }),
- },
- }
+func TestProhibitedPurposeSync(t *testing.T) {
+ vendorList34 := buildVendorList34()
+ vendorList34.Vendors["8"].Purposes = []int{7}
+ vendorListData := MarshalVendorList(vendorList34)
+
+ perms := allPurposesEnabledPermissions()
perms.cfg.HostVendorID = 8
+ perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderAppnexus: 2,
+ openrtb_ext.BidderPubmatic: 6,
+ openrtb_ext.BidderRubicon: 8,
+ }
+ perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+ 34: parseVendorListDataV2(t, vendorListData),
+ }),
+ }
- // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8
+ // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8
allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
assert.NoErrorf(t, err, "Error processing HostCookiesAllowed")
assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure")
@@ -680,26 +623,24 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) {
assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure")
}
-func TestProhibitedVendorSyncTCF2(t *testing.T) {
- vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34())
- perms := permissionsImpl{
- cfg: tcf2Config,
- vendorIDs: map[openrtb_ext.BidderName]uint16{
- openrtb_ext.BidderAppnexus: 2,
- openrtb_ext.BidderPubmatic: 6,
- openrtb_ext.BidderRubicon: 8,
- openrtb_ext.BidderOpenx: 10,
- },
- fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
- tcf1SpecVersion: nil,
- tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
- 34: parseVendorListDataV2(t, vendorListData),
- }),
- },
- }
+func TestProhibitedVendorSync(t *testing.T) {
+ vendorListData := MarshalVendorList(buildVendorList34())
+
+ perms := allPurposesEnabledPermissions()
perms.cfg.HostVendorID = 10
+ perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderAppnexus: 2,
+ openrtb_ext.BidderPubmatic: 6,
+ openrtb_ext.BidderRubicon: 8,
+ openrtb_ext.BidderOpenx: 10,
+ }
+ perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+ 34: parseVendorListDataV2(t, vendorListData),
+ }),
+ }
- // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8
+ // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8
allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
assert.NoErrorf(t, err, "Error processing HostCookiesAllowed")
assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure")
@@ -776,58 +717,350 @@ func assertStringsEqual(t *testing.T, expected string, actual string) {
func TestNormalizeGDPR(t *testing.T) {
tests := []struct {
- description string
- userSyncIfAmbiguous bool
- giveSignal Signal
- wantSignal Signal
+ description string
+ gdprDefaultValue string
+ giveSignal Signal
+ wantSignal Signal
}{
{
- description: "Don't normalize - Signal No and userSyncIfAmbiguous false",
- userSyncIfAmbiguous: false,
- giveSignal: SignalNo,
- wantSignal: SignalNo,
+ description: "Don't normalize - Signal No and gdprDefaultValue 1",
+ gdprDefaultValue: "1",
+ giveSignal: SignalNo,
+ wantSignal: SignalNo,
},
{
- description: "Don't normalize - Signal No and userSyncIfAmbiguous true",
- userSyncIfAmbiguous: true,
- giveSignal: SignalNo,
- wantSignal: SignalNo,
+ description: "Don't normalize - Signal No and gdprDefaultValue 0",
+ gdprDefaultValue: "0",
+ giveSignal: SignalNo,
+ wantSignal: SignalNo,
},
{
- description: "Don't normalize - Signal Yes and userSyncIfAmbiguous false",
- userSyncIfAmbiguous: false,
- giveSignal: SignalYes,
- wantSignal: SignalYes,
+ description: "Don't normalize - Signal Yes and gdprDefaultValue 1",
+ gdprDefaultValue: "1",
+ giveSignal: SignalYes,
+ wantSignal: SignalYes,
},
{
- description: "Don't normalize - Signal Yes and userSyncIfAmbiguous true",
- userSyncIfAmbiguous: true,
- giveSignal: SignalYes,
- wantSignal: SignalYes,
+ description: "Don't normalize - Signal Yes and gdprDefaultValue 0",
+ gdprDefaultValue: "0",
+ giveSignal: SignalYes,
+ wantSignal: SignalYes,
},
{
- description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous false",
- userSyncIfAmbiguous: false,
- giveSignal: SignalAmbiguous,
- wantSignal: SignalYes,
+ description: "Normalize - Signal Ambiguous and gdprDefaultValue 1",
+ gdprDefaultValue: "1",
+ giveSignal: SignalAmbiguous,
+ wantSignal: SignalYes,
},
{
- description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous true",
- userSyncIfAmbiguous: true,
- giveSignal: SignalAmbiguous,
- wantSignal: SignalNo,
+ description: "Normalize - Signal Ambiguous and gdprDefaultValue 0",
+ gdprDefaultValue: "0",
+ giveSignal: SignalAmbiguous,
+ wantSignal: SignalNo,
},
}
for _, tt := range tests {
perms := permissionsImpl{
cfg: config.GDPR{
- UsersyncIfAmbiguous: tt.userSyncIfAmbiguous,
+ DefaultValue: tt.gdprDefaultValue,
},
}
+ if tt.gdprDefaultValue == "0" {
+ perms.gdprDefaultValue = SignalNo
+ } else {
+ perms.gdprDefaultValue = SignalYes
+ }
+
normalizedSignal := perms.normalizeGDPR(tt.giveSignal)
assert.Equal(t, tt.wantSignal, normalizedSignal, tt.description)
}
}
+
+func TestAllowActivitiesBidRequests(t *testing.T) {
+ purpose2AndVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAADAQAAAAAA"
+ purpose2ConsentWithoutVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAABIAAAAA"
+
+ purpose2AndVendorLI := "CPF_61ePF_61eFxAAAENAiCAAAAAAEAAAAAAAAAAIAIAAA"
+ purpose2LIWithoutVendorLI := "CPF_61ePF_61eFxAAAENAiCAAAAAAEAAAAAAABIAAAAA"
+
+ testDefs := []struct {
+ description string
+ purpose2Enabled bool
+ purpose2EnforceVendors bool
+ bidder openrtb_ext.BidderName
+ consent string
+ allowBid bool
+ passGeo bool
+ passID bool
+ weakVendorEnforcement bool
+ }{
+ {
+ description: "Bid blocked - p2 enabled, user consents to p2 but not vendor, vendor consents to p2",
+ purpose2Enabled: true,
+ purpose2EnforceVendors: true,
+ bidder: openrtb_ext.BidderPubmatic,
+ consent: purpose2ConsentWithoutVendorConsent,
+ allowBid: false,
+ passGeo: false,
+ passID: false,
+ },
+ {
+ description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 but not vendor, vendor consents to p2",
+ purpose2Enabled: true,
+ purpose2EnforceVendors: false,
+ bidder: openrtb_ext.BidderPubmatic,
+ consent: purpose2ConsentWithoutVendorConsent,
+ allowBid: true,
+ passGeo: false,
+ passID: true,
+ },
+ {
+ description: "Bid allowed - p2 disabled, user consents to p2 but not vendor, vendor consents to p2",
+ purpose2Enabled: false,
+ purpose2EnforceVendors: true,
+ bidder: openrtb_ext.BidderPubmatic,
+ consent: purpose2ConsentWithoutVendorConsent,
+ allowBid: true,
+ passGeo: false,
+ passID: false,
+ },
+ {
+ description: "Bid allowed - p2 enabled, user consents to p2 and vendor, vendor consents to p2",
+ purpose2Enabled: true,
+ purpose2EnforceVendors: true,
+ bidder: openrtb_ext.BidderPubmatic,
+ consent: purpose2AndVendorConsent,
+ allowBid: true,
+ passGeo: false,
+ passID: true,
+ },
+ {
+ description: "Bid blocked - p2 enabled, user consents to p2 LI but not vendor, vendor consents to p2",
+ purpose2Enabled: true,
+ purpose2EnforceVendors: true,
+ bidder: openrtb_ext.BidderRubicon,
+ consent: purpose2LIWithoutVendorLI,
+ allowBid: false,
+ passGeo: false,
+ passID: false,
+ },
+ {
+ description: "Bid allowed - p2 enabled, user consents to p2 LI and vendor, vendor consents to p2",
+ purpose2Enabled: true,
+ purpose2EnforceVendors: true,
+ bidder: openrtb_ext.BidderRubicon,
+ consent: purpose2AndVendorLI,
+ allowBid: true,
+ passGeo: false,
+ passID: true,
+ },
+ {
+ description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 LI but not vendor, vendor consents to p2",
+ purpose2Enabled: true,
+ purpose2EnforceVendors: false,
+ bidder: openrtb_ext.BidderPubmatic,
+ consent: purpose2AndVendorLI,
+ allowBid: true,
+ passGeo: false,
+ passID: true,
+ },
+ }
+
+ for _, td := range testDefs {
+ vendorListData := MarshalVendorList(buildVendorList34())
+
+ perms := allPurposesEnabledPermissions()
+ perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderPubmatic: 6,
+ openrtb_ext.BidderRubicon: 8,
+ }
+ perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+ 34: parseVendorListDataV2(t, vendorListData),
+ }),
+ }
+ perms.cfg.TCF2.Purpose2.Enabled = td.purpose2Enabled
+ p2Config := perms.purposeConfigs[consentconstants.Purpose(2)]
+ p2Config.Enabled = td.purpose2Enabled
+ p2Config.EnforceVendors = td.purpose2EnforceVendors
+ perms.purposeConfigs[consentconstants.Purpose(2)] = p2Config
+
+ allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement)
+ assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
+ assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description)
+ assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description)
+ assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description)
+ }
+}
+
+func TestTCF1Consent(t *testing.T) {
+ bidderAllowedByConsent := openrtb_ext.BidderAppnexus
+ tcf1Consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA"
+
+ perms := permissionsImpl{
+ vendorIDs: map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderAppnexus: 2,
+ },
+ }
+
+ bidReq, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, "", SignalYes, tcf1Consent, false)
+
+ assert.Nil(t, err, "TCF1 consent - no error returned")
+ assert.Equal(t, false, bidReq, "TCF1 consent - bid request not allowed")
+ assert.Equal(t, false, passGeo, "TCF1 consent - passing geo not allowed")
+ assert.Equal(t, false, passID, "TCF1 consent - passing id not allowed")
+}
+
+func TestAllowActivitiesVendorException(t *testing.T) {
+ noPurposeOrVendorConsentAndPubRestrictsP2 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAACEAAgAgAA"
+ noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA"
+
+ testDefs := []struct {
+ description string
+ p2VendorExceptionMap map[openrtb_ext.BidderName]struct{}
+ sp1VendorExceptionMap map[openrtb_ext.BidderName]struct{}
+ bidder openrtb_ext.BidderName
+ consent string
+ allowBid bool
+ passGeo bool
+ passID bool
+ }{
+ {
+ description: "Bid/ID blocked by publisher - p2 enabled with p2 vendor exception, pub restricts p2 for vendor",
+ p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
+ bidder: openrtb_ext.BidderAppnexus,
+ consent: noPurposeOrVendorConsentAndPubRestrictsP2,
+ allowBid: false,
+ passGeo: false,
+ passID: false,
+ },
+ {
+ description: "Bid/ID allowed by vendor exception - p2 enabled with p2 vendor exception, pub restricts none",
+ p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
+ sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ bidder: openrtb_ext.BidderAppnexus,
+ consent: noPurposeOrVendorConsentAndPubRestrictsNone,
+ allowBid: true,
+ passGeo: false,
+ passID: true,
+ },
+ {
+ description: "Geo blocked - sp1 enabled but no consent",
+ p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ bidder: openrtb_ext.BidderAppnexus,
+ consent: noPurposeOrVendorConsentAndPubRestrictsNone,
+ allowBid: false,
+ passGeo: false,
+ passID: false,
+ },
+ {
+ description: "Geo allowed by vendor exception - sp1 enabled with sp1 vendor exception",
+ p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
+ bidder: openrtb_ext.BidderAppnexus,
+ consent: noPurposeOrVendorConsentAndPubRestrictsNone,
+ allowBid: false,
+ passGeo: true,
+ passID: false,
+ },
+ }
+
+ for _, td := range testDefs {
+ vendorListData := MarshalVendorList(buildVendorList34())
+ perms := permissionsImpl{
+ cfg: config.GDPR{
+ HostVendorID: 2,
+ TCF2: config.TCF2{
+ Enabled: true,
+ Purpose2: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p2VendorExceptionMap},
+ SpecialPurpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.sp1VendorExceptionMap},
+ },
+ },
+ vendorIDs: map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderAppnexus: 32,
+ },
+ fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+ 34: parseVendorListDataV2(t, vendorListData),
+ }),
+ },
+ }
+ perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
+ consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2,
+ consentconstants.Purpose(3): perms.cfg.TCF2.Purpose3,
+ }
+
+ allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, false)
+ assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
+ assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description)
+ assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description)
+ assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description)
+ }
+}
+
+func TestBidderSyncAllowedVendorException(t *testing.T) {
+ noPurposeOrVendorConsentAndPubRestrictsP1 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAAQAAAAAAAAAAIIACACA"
+ noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA"
+
+ testDefs := []struct {
+ description string
+ p1VendorExceptionMap map[openrtb_ext.BidderName]struct{}
+ bidder openrtb_ext.BidderName
+ consent string
+ allowSync bool
+ }{
+ {
+ description: "Sync blocked by no consent - p1 enabled, no p1 vendor exception, pub restricts none",
+ p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+ bidder: openrtb_ext.BidderAppnexus,
+ consent: noPurposeOrVendorConsentAndPubRestrictsNone,
+ allowSync: false,
+ },
+ {
+ description: "Sync blocked by publisher - p1 enabled with p1 vendor exception, pub restricts p1 for vendor",
+ p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
+ bidder: openrtb_ext.BidderAppnexus,
+ consent: noPurposeOrVendorConsentAndPubRestrictsP1,
+ allowSync: false,
+ },
+ {
+ description: "Sync allowed by vendor exception - p1 enabled with p1 vendor exception, pub restricts none",
+ p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
+ bidder: openrtb_ext.BidderAppnexus,
+ consent: noPurposeOrVendorConsentAndPubRestrictsNone,
+ allowSync: true,
+ },
+ }
+
+ for _, td := range testDefs {
+ vendorListData := MarshalVendorList(buildVendorList34())
+ perms := permissionsImpl{
+ cfg: config.GDPR{
+ HostVendorID: 2,
+ TCF2: config.TCF2{
+ Enabled: true,
+ Purpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p1VendorExceptionMap},
+ },
+ },
+ vendorIDs: map[openrtb_ext.BidderName]uint16{
+ openrtb_ext.BidderAppnexus: 32,
+ },
+ fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+ 34: parseVendorListDataV2(t, vendorListData),
+ }),
+ },
+ }
+ perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
+ consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1,
+ }
+
+ allowSync, err := perms.BidderSyncAllowed(context.Background(), td.bidder, SignalYes, td.consent)
+ assert.NoErrorf(t, err, "Error processing BidderSyncAllowed for %s", td.description)
+ assert.EqualValuesf(t, td.allowSync, allowSync, "AllowSync failure on %s", td.description)
+ }
+}
diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go
index bc7eab40647..24489e73265 100644
--- a/gdpr/vendorlist-fetching.go
+++ b/gdpr/vendorlist-fetching.go
@@ -26,33 +26,7 @@ type saveVendors func(uint16, api.VendorList)
//
// Nothing in this file is exported. Public APIs can be found in gdpr.go
-func newVendorListFetcherTCF1(cfg config.GDPR) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
- if len(cfg.TCF1.FallbackGVLPath) == 0 {
- return func(_ context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) {
- return nil, makeVendorListNotFoundError(vendorListVersion)
- }
- }
-
- fallback := loadFallbackGVLForTCF1(cfg.TCF1.FallbackGVLPath)
- return func(_ context.Context, _ uint16) (vendorlist.VendorList, error) {
- return fallback, nil
- }
-}
-
-func loadFallbackGVLForTCF1(fallbackGVLPath string) vendorlist.VendorList {
- fallbackContents, err := ioutil.ReadFile(fallbackGVLPath)
- if err != nil {
- glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err)
- }
-
- fallback, err := vendorlist.ParseEagerly(fallbackContents)
- if err != nil {
- glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err)
- }
- return fallback
-}
-
-func newVendorListFetcherTCF2(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
+func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
cacheSave, cacheLoad := newVendorListCache()
preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout())
diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go
index 27f1bc3b996..95529e4e334 100644
--- a/gdpr/vendorlist-fetching_test.go
+++ b/gdpr/vendorlist-fetching_test.go
@@ -14,71 +14,15 @@ import (
"github.com/prebid/prebid-server/config"
)
-func TestTCF1FetcherInitialLoad(t *testing.T) {
- // Loads two vendor lists during initialization by setting the latest vendor list version to 2.
-
- server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
- vendorListLatestVersion: 2,
- vendorLists: map[int]string{
- 1: tcf1VendorList1,
- 2: tcf1VendorList2,
- },
- })))
- defer server.Close()
-
- testCases := []test{
- {
- description: "Fallback - Vendor List 1",
- setup: testSetup{
- enableTCF1Fallback: true,
- vendorListVersion: 1,
- },
- expected: vendorListFallbackExpected,
- },
- {
- description: "Fallback - Vendor List 2",
- setup: testSetup{
- enableTCF1Fallback: true,
- vendorListVersion: 2,
- },
- expected: vendorListFallbackExpected,
- },
- {
- description: "No Fallback - Vendor List 1",
- setup: testSetup{
- enableTCF1Fallback: false,
- vendorListVersion: 1,
- },
- expected: testExpected{
- errorMessage: "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes",
- },
- },
- {
- description: "No Fallback - Vendor List 2",
- setup: testSetup{
- enableTCF1Fallback: false,
- vendorListVersion: 2,
- },
- expected: testExpected{
- errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes",
- },
- },
- }
-
- for _, test := range testCases {
- runTestTCF1(t, test, server)
- }
-}
-
-func TestTCF2FetcherDynamicLoadListExists(t *testing.T) {
+func TestFetcherDynamicLoadListExists(t *testing.T) {
// Loads the first vendor list during initialization by setting the latest vendor list version to 1.
// All other vendor lists will be dynamically loaded.
server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
vendorListLatestVersion: 1,
vendorLists: map[int]string{
- 1: tcf2VendorList1,
- 2: tcf2VendorList2,
+ 1: vendorList1,
+ 2: vendorList2,
},
})))
defer server.Close()
@@ -91,17 +35,17 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) {
expected: vendorList2Expected,
}
- runTestTCF2(t, test, server)
+ runTest(t, test, server)
}
-func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) {
+func TestFetcherDynamicLoadListDoesntExist(t *testing.T) {
// Loads the first vendor list during initialization by setting the latest vendor list version to 1.
// All other vendor list load attempts will be done dynamically.
server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
vendorListLatestVersion: 1,
vendorLists: map[int]string{
- 1: tcf2VendorList1,
+ 1: vendorList1,
},
})))
defer server.Close()
@@ -116,30 +60,30 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) {
},
}
- runTestTCF2(t, test, server)
+ runTest(t, test, server)
}
-func TestTCF2FetcherThrottling(t *testing.T) {
+func TestFetcherThrottling(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
vendorListLatestVersion: 1,
vendorLists: map[int]string{
- 1: tcf2MarshalVendorList(tcf2VendorList{
+ 1: MarshalVendorList(vendorList{
VendorListVersion: 1,
- Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1}}},
+ Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1}}},
}),
- 2: tcf2MarshalVendorList(tcf2VendorList{
+ 2: MarshalVendorList(vendorList{
VendorListVersion: 2,
- Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2}}},
+ Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2}}},
}),
- 3: tcf2MarshalVendorList(tcf2VendorList{
+ 3: MarshalVendorList(vendorList{
VendorListVersion: 3,
- Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}},
+ Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}},
}),
},
})))
defer server.Close()
- fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server))
+ fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server))
// Dynamically Load List 2 Successfully
_, errList1 := fetcher(context.Background(), 2)
@@ -151,7 +95,7 @@ func TestTCF2FetcherThrottling(t *testing.T) {
assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes")
}
-func TestTCF2MalformedVendorlist(t *testing.T) {
+func TestMalformedVendorlist(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
vendorListLatestVersion: 1,
vendorLists: map[int]string{
@@ -160,30 +104,30 @@ func TestTCF2MalformedVendorlist(t *testing.T) {
})))
defer server.Close()
- fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server))
+ fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server))
_, err := fetcher(context.Background(), 1)
// Fetching should fail since vendor list could not be unmarshalled.
assert.Error(t, err)
}
-func TestTCF2ServerUrlInvalid(t *testing.T) {
+func TestServerUrlInvalid(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
server.Close()
invalidURLGenerator := func(uint16) string { return " http://invalid-url-has-leading-whitespace" }
- fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), invalidURLGenerator)
+ fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator)
_, err := fetcher(context.Background(), 1)
assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes")
}
-func TestTCF2ServerUnavailable(t *testing.T) {
+func TestServerUnavailable(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
server.Close()
- fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server))
+ fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server))
_, err := fetcher(context.Background(), 1)
assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes")
@@ -213,24 +157,14 @@ func TestVendorListURLMaker(t *testing.T) {
}
}
-var tcf1VendorList1 = tcf1MarshalVendorList(tcf1VendorList{
+var vendorList1 = MarshalVendorList(vendorList{
VendorListVersion: 1,
- Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2}}},
+ Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2}}},
})
-var tcf2VendorList1 = tcf2MarshalVendorList(tcf2VendorList{
- VendorListVersion: 1,
- Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2}}},
-})
-
-var tcf1VendorList2 = tcf1MarshalVendorList(tcf1VendorList{
+var vendorList2 = MarshalVendorList(vendorList{
VendorListVersion: 2,
- Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2, 3}}},
-})
-
-var tcf2VendorList2 = tcf2MarshalVendorList(tcf2VendorList{
- VendorListVersion: 2,
- Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2, 3}}},
+ Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2, 3}}},
})
var vendorList2Expected = testExpected{
@@ -245,27 +179,12 @@ var vendorListFallbackExpected = testExpected{
vendorPurposes: map[int]bool{1: true, 2: false, 3: true},
}
-type tcf1VendorList struct {
- VendorListVersion uint16 `json:"vendorListVersion"`
- Vendors []tcf1Vendor `json:"vendors"`
+type vendorList struct {
+ VendorListVersion uint16 `json:"vendorListVersion"`
+ Vendors map[string]*vendor `json:"vendors"`
}
-type tcf1Vendor struct {
- ID uint16 `json:"id"`
- Purposes []int `json:"purposeIds"`
-}
-
-func tcf1MarshalVendorList(vendorList tcf1VendorList) string {
- json, _ := json.Marshal(vendorList)
- return string(json)
-}
-
-type tcf2VendorList struct {
- VendorListVersion uint16 `json:"vendorListVersion"`
- Vendors map[string]*tcf2Vendor `json:"vendors"`
-}
-
-type tcf2Vendor struct {
+type vendor struct {
ID uint16 `json:"id"`
Purposes []int `json:"purposes"`
LegIntPurposes []int `json:"legIntPurposes"`
@@ -273,7 +192,7 @@ type tcf2Vendor struct {
SpecialPurposes []int `json:"specialPurposes"`
}
-func tcf2MarshalVendorList(vendorList tcf2VendorList) string {
+func MarshalVendorList(vendorList vendorList) string {
json, _ := json.Marshal(vendorList)
return string(json)
}
@@ -323,8 +242,7 @@ type test struct {
}
type testSetup struct {
- enableTCF1Fallback bool
- vendorListVersion uint16
+ vendorListVersion uint16
}
type testExpected struct {
@@ -334,31 +252,9 @@ type testExpected struct {
vendorPurposes map[int]bool
}
-func runTestTCF1(t *testing.T, test test, server *httptest.Server) {
+func runTest(t *testing.T, test test, server *httptest.Server) {
config := testConfig()
- if test.setup.enableTCF1Fallback {
- config.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json"
- }
-
- fetcher := newVendorListFetcherTCF1(config)
- vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion)
-
- if test.expected.errorMessage != "" {
- assert.EqualError(t, err, test.expected.errorMessage, test.description+":error")
- } else {
- assert.NoError(t, err, test.description+":vendorlist")
- assert.Equal(t, test.expected.vendorListVersion, vendorList.Version(), test.description+":vendorlistid")
- vendor := vendorList.Vendor(test.expected.vendorID)
- for id, expected := range test.expected.vendorPurposes {
- result := vendor.Purpose(consentconstants.Purpose(id))
- assert.Equalf(t, expected, result, "%s:vendor-%d:purpose-%d", test.description, vendorList.Version(), id)
- }
- }
-}
-
-func runTestTCF2(t *testing.T, test test, server *httptest.Server) {
- config := testConfig()
- fetcher := newVendorListFetcherTCF2(context.Background(), config, server.Client(), testURLMaker(server))
+ fetcher := newVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server))
vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion)
if test.expected.errorMessage != "" {
@@ -387,8 +283,5 @@ func testConfig() config.GDPR {
InitVendorlistFetch: 60 * 1000,
ActiveVendorlistFetch: 1000 * 5,
},
- TCF1: config.TCF1{
- FetchGVL: true,
- },
}
}
diff --git a/go.mod b/go.mod
index dee3615b79b..6228581eaae 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/prebid/prebid-server
-go 1.14
+go 1.16
require (
github.com/BurntSushi/toml v0.3.1 // indirect
@@ -11,7 +11,7 @@ require (
github.com/beevik/etree v1.0.2
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
github.com/blang/semver v3.5.1+incompatible
- github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
+ github.com/buger/jsonparser v1.1.1
github.com/cespare/xxhash v1.0.0 // indirect
github.com/chasex/glog v0.0.0-20160217080310-c62392af379c
github.com/coocood/freecache v1.0.1
@@ -23,15 +23,15 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/influxdata/influxdb v1.6.1
github.com/julienschmidt/httprouter v1.1.0
- github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
github.com/lib/pq v1.0.0
github.com/magiconair/properties v1.8.0
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+ github.com/mitchellh/copystructure v1.1.2
github.com/mitchellh/mapstructure v1.0.0 // indirect
github.com/mxmCherry/openrtb/v15 v15.0.0
github.com/pelletier/go-toml v1.2.0 // indirect
- github.com/prebid/go-gdpr v0.8.3
+ github.com/prebid/go-gdpr v0.9.0
github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect
@@ -46,7 +46,7 @@ require (
github.com/spf13/pflag v1.0.2 // indirect
github.com/spf13/viper v1.1.0
github.com/stretchr/objx v0.1.1 // indirect
- github.com/stretchr/testify v1.5.1
+ github.com/stretchr/testify v1.7.0
github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
@@ -55,7 +55,7 @@ require (
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb
- golang.org/x/text v0.3.3
+ golang.org/x/text v0.3.6
gopkg.in/yaml.v2 v2.4.0
)
diff --git a/go.sum b/go.sum
index 0ccf122d248..d8cbf58754a 100644
--- a/go.sum
+++ b/go.sum
@@ -25,6 +25,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb
github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
+github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cespare/xxhash v1.0.0 h1:naDmySfoNg0nKS62/ujM6e71ZgM2AoVdaqGwMG0w18A=
github.com/cespare/xxhash v1.0.0/go.mod h1:fX/lfQBkSCDXZSUgv6jVIu/EVA3/JNseAX5asI4c4T4=
github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s=
@@ -70,6 +72,11 @@ github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4
github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
@@ -80,15 +87,25 @@ github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0=
+github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
+github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
+github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/mxmCherry/openrtb/v15 v15.0.0 h1:inLuQ3Bsima9HLB2v6WjbtEFF69SWOT5Dux4QZtYdrw=
+github.com/mxmCherry/openrtb/v15 v15.0.0/go.mod h1:TVgncsz6MOzbL7lhun1lNuUBzVBlVDbxf9Fyy1TyhZA=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54=
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug=
@@ -97,8 +114,8 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prebid/go-gdpr v0.8.3 h1:rjCZNV0AdKygiGHpVhNB42usjEpTN3qidXUPB1yarb0=
-github.com/prebid/go-gdpr v0.8.3/go.mod h1:TGzgqQDGKOVUkbqmY25K4uvcwMywSddXEaY4zUFiVBQ=
+github.com/prebid/go-gdpr v0.9.0 h1:FL1ZXuccMYOPIt69mIHF2AyRhv8ezvtjnUoAE3Ph8O0=
+github.com/prebid/go-gdpr v0.9.0/go.mod h1:OfBxLfd+JfP3OAJ1MhI4JYAV3dSMQYT1QAb80DHpZFo=
github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg=
github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
@@ -129,8 +146,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 h1:Va10CytCCYRm4xBTses5ZDeDjeIQjhaiC9nRCe/yflI=
github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303/go.mod h1:Xdcad1nGVhQfhoV0go+/4WaI/RZkWlvfjkVCdpMTxPY=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
@@ -139,6 +157,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY=
github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
+github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM=
+github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y=
github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M=
github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
@@ -172,8 +192,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -199,3 +220,5 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
index 294cc141169..e73df6272c6 100644
--- a/main.go
+++ b/main.go
@@ -37,12 +37,12 @@ func main() {
cfg, err := loadConfig()
if err != nil {
- glog.Fatalf("Configuration could not be loaded or did not pass validation: %v", err)
+ glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err)
}
err = serve(Rev, cfg)
if err != nil {
- glog.Errorf("prebid-server failed: %v", err)
+ glog.Exitf("prebid-server failed: %v", err)
}
}
*/
@@ -59,7 +59,7 @@ func InitPrebidServer(configFile string) {
err = serve(Rev, cfg)
if err != nil {
- glog.Errorf("prebid-server failed: %v", err)
+ glog.Exitf("prebid-server failed: %v", err)
}
}
diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go
index 6c9344b325b..e9a436eed49 100644
--- a/metrics/config/metrics.go
+++ b/metrics/config/metrics.go
@@ -147,9 +147,9 @@ func (me *MultiMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) {
}
}
-func (me *MultiMetricsEngine) RecordTLSHandshakeTime(bidderName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) {
+func (me *MultiMetricsEngine) RecordTLSHandshakeTime(adapterName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) {
for _, thisME := range *me {
- thisME.RecordTLSHandshakeTime(bidderName, tlsHandshakeTime)
+ thisME.RecordTLSHandshakeTime(adapterName, tlsHandshakeTime)
}
}
@@ -286,9 +286,34 @@ func (me *MultiMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.Adapt
}
}
+// RecordAdapterGDPRRequestBlocked across all engines
+func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) {
+ for _, thisME := range *me {
+ thisME.RecordAdapterGDPRRequestBlocked(adapter)
+ }
+}
+
// DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests)
type DummyMetricsEngine struct{}
+func (me *DummyMetricsEngine) RecordAdapterDuplicateBidID(adaptor string, collisions int) {
+}
+
+func (me *DummyMetricsEngine) RecordRequestHavingDuplicateBidID() {
+}
+
+func (me *DummyMetricsEngine) RecordPodImpGenTime(labels metrics.PodLabels, startTime time.Time) {
+}
+
+func (me *DummyMetricsEngine) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) {
+}
+
+func (me *DummyMetricsEngine) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) {
+}
+
+func (me *DummyMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) {
+}
+
// RecordRequest as a noop
func (me *DummyMetricsEngine) RecordRequest(labels metrics.Labels) {
}
@@ -338,7 +363,7 @@ func (me *DummyMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) {
}
// RecordTLSHandshakeTime as a noop
-func (me *DummyMetricsEngine) RecordTLSHandshakeTime(bidderName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) {
+func (me *DummyMetricsEngine) RecordTLSHandshakeTime(adapterName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) {
}
// RecordAdapterBidReceived as a noop
@@ -393,26 +418,6 @@ func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) {
func (me *DummyMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels) {
}
-// RecordAdapterDuplicateBidID as a noop
-func (me *DummyMetricsEngine) RecordAdapterDuplicateBidID(adaptor string, collisions int) {
-}
-
-// RecordRequestHavingDuplicateBidID as a noop
-func (me *DummyMetricsEngine) RecordRequestHavingDuplicateBidID() {
-}
-
-// RecordPodImpGenTime as a noop
-func (me *DummyMetricsEngine) RecordPodImpGenTime(labels metrics.PodLabels, start time.Time) {
-}
-
-// RecordPodCombGenTime as a noop
-func (me *DummyMetricsEngine) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) {
-}
-
-// RecordPodCompititveExclusionTime as a noop
-func (me *DummyMetricsEngine) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) {
-}
-
-// RecordAdapterVideoBidDuration as a noop
-func (me *DummyMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) {
+// RecordAdapterGDPRRequestBlocked as a noop
+func (me *DummyMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) {
}
diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go
index 5b70b53bb1a..7f9643330e3 100644
--- a/metrics/config/metrics_test.go
+++ b/metrics/config/metrics_test.go
@@ -118,6 +118,8 @@ func TestMultiMetricsEngine(t *testing.T) {
metricsEngine.RecordStoredImpCacheResult(metrics.CacheHit, 5)
metricsEngine.RecordAccountCacheResult(metrics.CacheHit, 6)
+ metricsEngine.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus)
+
metricsEngine.RecordRequestQueueTime(false, metrics.ReqTypeVideo, time.Duration(1))
//Make the metrics engine, instantiated here with goEngine, fill its RequestStatuses[RequestType][metrics.RequestStatusXX] with the new boolean values added to metrics.Labels
@@ -161,6 +163,8 @@ func TestMultiMetricsEngine(t *testing.T) {
VerifyMetrics(t, "StoredReqCache.Hit", goEngine.StoredReqCacheMeter[metrics.CacheHit].Count(), 4)
VerifyMetrics(t, "StoredImpCache.Hit", goEngine.StoredImpCacheMeter[metrics.CacheHit].Count(), 5)
VerifyMetrics(t, "AccountCache.Hit", goEngine.AccountCacheMeter[metrics.CacheHit].Count(), 6)
+
+ VerifyMetrics(t, "AdapterMetrics.AppNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), 1)
}
func VerifyMetrics(t *testing.T, name string, actual int64, expected int64) {
diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go
index 2529eaf4765..45dece19f7d 100644
--- a/metrics/go_metrics.go
+++ b/metrics/go_metrics.go
@@ -88,19 +88,20 @@ type Metrics struct {
// AdapterMetrics houses the metrics for a particular adapter
type AdapterMetrics struct {
- NoCookieMeter metrics.Meter
- ErrorMeters map[AdapterError]metrics.Meter
- NoBidMeter metrics.Meter
- GotBidsMeter metrics.Meter
- RequestTimer metrics.Timer
- PriceHistogram metrics.Histogram
- BidsReceivedMeter metrics.Meter
- PanicMeter metrics.Meter
- MarkupMetrics map[openrtb_ext.BidType]*MarkupDeliveryMetrics
- ConnCreated metrics.Counter
- ConnReused metrics.Counter
- ConnWaitTime metrics.Timer
- TLSHandshakeTimer metrics.Timer
+ NoCookieMeter metrics.Meter
+ ErrorMeters map[AdapterError]metrics.Meter
+ NoBidMeter metrics.Meter
+ GotBidsMeter metrics.Meter
+ RequestTimer metrics.Timer
+ PriceHistogram metrics.Histogram
+ BidsReceivedMeter metrics.Meter
+ PanicMeter metrics.Meter
+ MarkupMetrics map[openrtb_ext.BidType]*MarkupDeliveryMetrics
+ ConnCreated metrics.Counter
+ ConnReused metrics.Counter
+ ConnWaitTime metrics.Timer
+ GDPRRequestBlocked metrics.Meter
+ TLSHandshakeTimer metrics.Timer
}
type MarkupDeliveryMetrics struct {
@@ -321,6 +322,9 @@ func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMet
newAdapter.ConnWaitTime = &metrics.NilTimer{}
newAdapter.TLSHandshakeTimer = &metrics.NilTimer{}
}
+ if !disabledMetrics.AdapterGDPRRequestBlocked {
+ newAdapter.GDPRRequestBlocked = blankMeter
+ }
for _, err := range AdapterErrors() {
newAdapter.ErrorMeters[err] = blankMeter
}
@@ -366,6 +370,7 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string,
am.BidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.bids_received", adapterOrAccount, exchange), registry)
}
am.PanicMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.requests.panic", adapterOrAccount, exchange), registry)
+ am.GDPRRequestBlocked = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.gdpr_request_blocked", adapterOrAccount, exchange), registry)
}
func makeDeliveryMetrics(registry metrics.Registry, prefix string, bidType openrtb_ext.BidType) *MarkupDeliveryMetrics {
@@ -730,6 +735,20 @@ func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) {
return
}
+func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) {
+ if me.MetricsDisabled.AdapterGDPRRequestBlocked {
+ return
+ }
+
+ am, ok := me.AdapterMetrics[adapterName]
+ if !ok {
+ glog.Errorf("Trying to log adapter GDPR request blocked metric for %s: adapter not found", string(adapterName))
+ return
+ }
+
+ am.GDPRRequestBlocked.Mark(1)
+}
+
// RecordAdapterDuplicateBidID as noop
func (me *Metrics) RecordAdapterDuplicateBidID(adaptor string, collisions int) {
}
diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go
index 7ebe2f3c2fe..ed9df52a51e 100644
--- a/metrics/go_metrics_test.go
+++ b/metrics/go_metrics_test.go
@@ -60,7 +60,6 @@ func TestNewMetrics(t *testing.T) {
ensureContains(t, registry, "privacy.request.ccpa.opt-out", m.PrivacyCCPARequestOptOut)
ensureContains(t, registry, "privacy.request.coppa", m.PrivacyCOPPARequest)
ensureContains(t, registry, "privacy.request.lmt", m.PrivacyLMTRequest)
- ensureContains(t, registry, "privacy.request.tcf.v1", m.PrivacyTCFRequestVersion[TCFVersionV1])
ensureContains(t, registry, "privacy.request.tcf.v2", m.PrivacyTCFRequestVersion[TCFVersionV2])
ensureContains(t, registry, "privacy.request.tcf.err", m.PrivacyTCFRequestVersion[TCFVersionErr])
}
@@ -570,28 +569,58 @@ func TestRecordRequestPrivacy(t *testing.T) {
GDPREnforced: true,
GDPRTCFVersion: TCFVersionErr,
})
- m.RecordRequestPrivacy(PrivacyLabels{
- GDPREnforced: true,
- GDPRTCFVersion: TCFVersionV1,
- })
m.RecordRequestPrivacy(PrivacyLabels{
GDPREnforced: true,
GDPRTCFVersion: TCFVersionV2,
})
- m.RecordRequestPrivacy(PrivacyLabels{
- GDPREnforced: true,
- GDPRTCFVersion: TCFVersionV1,
- })
assert.Equal(t, m.PrivacyCCPARequest.Count(), int64(2), "CCPA")
assert.Equal(t, m.PrivacyCCPARequestOptOut.Count(), int64(1), "CCPA Opt Out")
assert.Equal(t, m.PrivacyCOPPARequest.Count(), int64(1), "COPPA")
assert.Equal(t, m.PrivacyLMTRequest.Count(), int64(1), "LMT")
assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionErr].Count(), int64(1), "TCF Err")
- assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV1].Count(), int64(2), "TCF V1")
assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2")
}
+func TestRecordAdapterGDPRRequestBlocked(t *testing.T) {
+ var fakeBidder openrtb_ext.BidderName = "fooAdvertising"
+
+ tests := []struct {
+ description string
+ metricsDisabled bool
+ adapterName openrtb_ext.BidderName
+ expectedCount int64
+ }{
+ {
+ description: "",
+ metricsDisabled: false,
+ adapterName: openrtb_ext.BidderAppnexus,
+ expectedCount: 1,
+ },
+ {
+ description: "",
+ metricsDisabled: false,
+ adapterName: fakeBidder,
+ expectedCount: 0,
+ },
+ {
+ description: "",
+ metricsDisabled: true,
+ adapterName: openrtb_ext.BidderAppnexus,
+ expectedCount: 0,
+ },
+ }
+
+ for _, tt := range tests {
+ registry := metrics.NewRegistry()
+ m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled})
+
+ m.RecordAdapterGDPRRequestBlocked(tt.adapterName)
+
+ assert.Equal(t, tt.expectedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), tt.description)
+ }
+}
+
func ensureContainsBidTypeMetrics(t *testing.T, registry metrics.Registry, prefix string, mdm map[openrtb_ext.BidType]*MarkupDeliveryMetrics) {
ensureContains(t, registry, prefix+".banner.adm_bids_received", mdm[openrtb_ext.BidTypeBanner].AdmMeter)
ensureContains(t, registry, prefix+".banner.nurl_bids_received", mdm[openrtb_ext.BidTypeBanner].NurlMeter)
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 5966b7716f3..7632ab1812d 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -308,7 +308,6 @@ type TCFVersionValue string
const (
TCFVersionErr TCFVersionValue = "err"
- TCFVersionV1 TCFVersionValue = "v1"
TCFVersionV2 TCFVersionValue = "v2"
)
@@ -316,7 +315,6 @@ const (
func TCFVersions() []TCFVersionValue {
return []TCFVersionValue{
TCFVersionErr,
- TCFVersionV1,
TCFVersionV2,
}
}
@@ -324,8 +322,6 @@ func TCFVersions() []TCFVersionValue {
// TCFVersionToValue takes an integer TCF version and returns the corresponding TCFVersionValue
func TCFVersionToValue(version int) TCFVersionValue {
switch {
- case version == 1:
- return TCFVersionV1
case version == 2:
return TCFVersionV2
}
@@ -367,6 +363,7 @@ type MetricsEngine interface {
RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
RecordTimeoutNotice(sucess bool)
RecordRequestPrivacy(privacy PrivacyLabels)
+ RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName)
// RecordAdapterDuplicateBidID captures the bid.ID collisions when adaptor
// gives the bid response with multiple bids containing same bid.ID
diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go
index b211b2faa22..8cc91b31c8a 100644
--- a/metrics/metrics_mock.go
+++ b/metrics/metrics_mock.go
@@ -141,6 +141,11 @@ func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) {
me.Called(privacy)
}
+// RecordAdapterGDPRRequestBlocked mock
+func (me *MetricsEngineMock) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) {
+ me.Called(adapterName)
+}
+
// RecordAdapterDuplicateBidID mock
func (me *MetricsEngineMock) RecordAdapterDuplicateBidID(adaptor string, collisions int) {
me.Called(adaptor, collisions)
@@ -169,4 +174,4 @@ func (me *MetricsEngineMock) RecordPodCompititveExclusionTime(labels PodLabels,
//RecordAdapterVideoBidDuration mock
func (me *MetricsEngineMock) RecordAdapterVideoBidDuration(labels AdapterLabels, videoBidDuration int) {
me.Called(labels, videoBidDuration)
-}
+}
\ No newline at end of file
diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go
index f4dfe43469d..621ad808619 100644
--- a/metrics/prometheus/preload.go
+++ b/metrics/prometheus/preload.go
@@ -178,6 +178,12 @@ func preloadLabelValues(m *Metrics) {
sourceLabel: sourceValues,
versionLabel: tcfVersionsAsString(),
})
+
+ if !m.metricsDisabled.AdapterGDPRRequestBlocked {
+ preloadLabelValuesForCounter(m.adapterGDPRBlockedRequests, map[string][]string{
+ adapterLabel: adapterValues,
+ })
+ }
}
func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) {
diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go
index a0a13455493..1a854621372 100644
--- a/metrics/prometheus/prometheus.go
+++ b/metrics/prometheus/prometheus.go
@@ -41,7 +41,7 @@ type Metrics struct {
storedVideoErrors *prometheus.CounterVec
timeoutNotifications *prometheus.CounterVec
dnsLookupTimer prometheus.Histogram
- //tlsHandhakeTimer prometheus.Histogram
+ //tlsHandhakeTimer prometheus.Histogram
privacyCCPA *prometheus.CounterVec
privacyCOPPA *prometheus.CounterVec
privacyLMT *prometheus.CounterVec
@@ -63,6 +63,7 @@ type Metrics struct {
adapterDuplicateBidIDCounter *prometheus.CounterVec
adapterVideoBidDuration *prometheus.HistogramVec
tlsHandhakeTimer *prometheus.HistogramVec
+ adapterGDPRBlockedRequests *prometheus.CounterVec
// Account Metrics
accountRequests *prometheus.CounterVec
@@ -309,6 +310,13 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet
"Count of total requests to Prebid Server where the LMT flag was set by source",
[]string{sourceLabel})
+ if !metrics.metricsDisabled.AdapterGDPRRequestBlocked {
+ metrics.adapterGDPRBlockedRequests = newCounter(cfg, metrics.Registry,
+ "adapter_gdpr_requests_blocked",
+ "Count of total bidder requests blocked due to unsatisfied GDPR purpose 2 legal basis",
+ []string{adapterLabel})
+ }
+
metrics.adapterBids = newCounter(cfg, metrics.Registry,
"adapter_bids",
"Count of bids labeled by adapter and markup delivery type (adm or nurl).",
@@ -764,6 +772,16 @@ func (m *Metrics) RecordRequestPrivacy(privacy metrics.PrivacyLabels) {
}
}
+func (m *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) {
+ if m.metricsDisabled.AdapterGDPRRequestBlocked {
+ return
+ }
+
+ m.adapterGDPRBlockedRequests.With(prometheus.Labels{
+ adapterLabel: string(adapterName),
+ }).Inc()
+}
+
// RecordAdapterDuplicateBidID captures the bid.ID collisions when adaptor
// gives the bid response with multiple bids containing same bid.ID
// ensure collisions value is greater than 1. This function will not give any error
diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go
index 2cdf8702364..b2e383140ce 100644
--- a/metrics/prometheus/prometheus_test.go
+++ b/metrics/prometheus/prometheus_test.go
@@ -1364,18 +1364,22 @@ func TestRecordAdapterConnections(t *testing.T) {
}
}
-func TestDisableAdapterConnections(t *testing.T) {
+func TestDisabledMetrics(t *testing.T) {
prometheusMetrics := NewMetrics(config.PrometheusMetrics{
Port: 8080,
Namespace: "prebid",
Subsystem: "server",
- }, config.DisabledMetrics{AdapterConnectionMetrics: true})
+ }, config.DisabledMetrics{
+ AdapterConnectionMetrics: true,
+ AdapterGDPRRequestBlocked: true,
+ })
// Assert counter vector was not initialized
assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil")
assert.Nil(t, prometheusMetrics.adapterCreatedConnections, "Counter Vector adapterCreatedConnections should be nil")
assert.Nil(t, prometheusMetrics.adapterConnectionWaitTime, "Counter Vector adapterConnectionWaitTime should be nil")
assert.Nil(t, prometheusMetrics.tlsHandhakeTimer, "Counter Vector tlsHandhakeTimer should be nil")
+ assert.Nil(t, prometheusMetrics.adapterGDPRBlockedRequests, "Counter Vector adapterGDPRBlockedRequests should be nil")
}
func TestRecordRequestPrivacy(t *testing.T) {
@@ -1410,18 +1414,10 @@ func TestRecordRequestPrivacy(t *testing.T) {
GDPREnforced: true,
GDPRTCFVersion: metrics.TCFVersionErr,
})
- m.RecordRequestPrivacy(metrics.PrivacyLabels{
- GDPREnforced: true,
- GDPRTCFVersion: metrics.TCFVersionV1,
- })
m.RecordRequestPrivacy(metrics.PrivacyLabels{
GDPREnforced: true,
GDPRTCFVersion: metrics.TCFVersionV2,
})
- m.RecordRequestPrivacy(metrics.PrivacyLabels{
- GDPREnforced: true,
- GDPRTCFVersion: metrics.TCFVersionV1,
- })
assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA,
float64(1),
@@ -1456,13 +1452,6 @@ func TestRecordRequestPrivacy(t *testing.T) {
versionLabel: "err",
})
- assertCounterVecValue(t, "", "privacy_tcf:v1", m.privacyTCF,
- float64(2),
- prometheus.Labels{
- sourceLabel: sourceRequest,
- versionLabel: "v1",
- })
-
assertCounterVecValue(t, "", "privacy_tcf:v2", m.privacyTCF,
float64(1),
prometheus.Labels{
@@ -1715,3 +1704,18 @@ func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expecte
assert.Equal(t, expectedCount, histogram.GetSampleCount(), name+":count")
assert.Equal(t, expectedSum, histogram.GetSampleSum(), name+":sum")
}
+
+func TestRecordAdapterGDPRRequestBlocked(t *testing.T) {
+ m := createMetricsForTesting()
+
+ m.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus)
+
+ assertCounterVecValue(t,
+ "Increment adapter GDPR request blocked counter",
+ "adapter_gdpr_requests_blocked",
+ m.adapterGDPRBlockedRequests,
+ 1,
+ prometheus.Labels{
+ adapterLabel: string(openrtb_ext.BidderAppnexus),
+ })
+}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
old mode 100755
new mode 100644
index 7d5684600bb..71b2146f4c3
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -73,115 +73,129 @@ func IsBidderNameReserved(name string) bool {
//
// Please keep this list alphabetized to minimize merge conflicts.
const (
- Bidder33Across BidderName = "33across"
- BidderAcuityAds BidderName = "acuityads"
- BidderAdform BidderName = "adform"
- BidderAdgeneration BidderName = "adgeneration"
- BidderAdhese BidderName = "adhese"
- BidderAdkernel BidderName = "adkernel"
- BidderAdkernelAdn BidderName = "adkernelAdn"
- BidderAdman BidderName = "adman"
- BidderAdmixer BidderName = "admixer"
- BidderAdOcean BidderName = "adocean"
- BidderAdoppler BidderName = "adoppler"
- BidderAdot BidderName = "adot"
- BidderAdpone BidderName = "adpone"
- BidderAdprime BidderName = "adprime"
- BidderAdtarget BidderName = "adtarget"
- BidderAdtelligent BidderName = "adtelligent"
- BidderAdvangelists BidderName = "advangelists"
- BidderAdxcg BidderName = "adxcg"
- BidderAdyoulike BidderName = "adyoulike"
- BidderAJA BidderName = "aja"
- BidderAMX BidderName = "amx"
- BidderApplogy BidderName = "applogy"
- BidderAppnexus BidderName = "appnexus"
- BidderAudienceNetwork BidderName = "audienceNetwork"
- BidderAvocet BidderName = "avocet"
- BidderBeachfront BidderName = "beachfront"
- BidderBeintoo BidderName = "beintoo"
- BidderBetween BidderName = "between"
- BidderBidmachine BidderName = "bidmachine"
- BidderBrightroll BidderName = "brightroll"
- BidderColossus BidderName = "colossus"
- BidderConnectAd BidderName = "connectad"
- BidderConsumable BidderName = "consumable"
- BidderConversant BidderName = "conversant"
- BidderCpmstar BidderName = "cpmstar"
- BidderCriteo BidderName = "criteo"
- BidderDatablocks BidderName = "datablocks"
- BidderDmx BidderName = "dmx"
- BidderDecenterAds BidderName = "decenterads"
- BidderDeepintent BidderName = "deepintent"
- BidderEmxDigital BidderName = "emx_digital"
- BidderEngageBDR BidderName = "engagebdr"
- BidderEPlanning BidderName = "eplanning"
- BidderEpom BidderName = "epom"
- BidderGamma BidderName = "gamma"
- BidderGamoshi BidderName = "gamoshi"
- BidderGrid BidderName = "grid"
- BidderGumGum BidderName = "gumgum"
- BidderImprovedigital BidderName = "improvedigital"
- BidderInMobi BidderName = "inmobi"
- BidderInvibes BidderName = "invibes"
- BidderIx BidderName = "ix"
- BidderJixie BidderName = "jixie"
- BidderKidoz BidderName = "kidoz"
- BidderKrushmedia BidderName = "krushmedia"
- BidderKubient BidderName = "kubient"
- BidderLifestreet BidderName = "lifestreet"
- BidderLockerDome BidderName = "lockerdome"
- BidderLogicad BidderName = "logicad"
- BidderLunaMedia BidderName = "lunamedia"
- BidderMarsmedia BidderName = "marsmedia"
- BidderMediafuse BidderName = "mediafuse"
- BidderMgid BidderName = "mgid"
- BidderMobfoxpb BidderName = "mobfoxpb"
- BidderMobileFuse BidderName = "mobilefuse"
- BidderNanoInteractive BidderName = "nanointeractive"
- BidderNinthDecimal BidderName = "ninthdecimal"
- BidderNoBid BidderName = "nobid"
- BidderOneTag BidderName = "onetag"
- BidderOpenx BidderName = "openx"
- BidderOrbidder BidderName = "orbidder"
- BidderOutbrain BidderName = "outbrain"
- BidderPangle BidderName = "pangle"
- BidderPubmatic BidderName = "pubmatic"
- BidderPubnative BidderName = "pubnative"
- BidderPulsepoint BidderName = "pulsepoint"
- BidderRevcontent BidderName = "revcontent"
- BidderRhythmone BidderName = "rhythmone"
- BidderRTBHouse BidderName = "rtbhouse"
- BidderRubicon BidderName = "rubicon"
- BidderSharethrough BidderName = "sharethrough"
- BidderSilverMob BidderName = "silvermob"
- BidderSmaato BidderName = "smaato"
- BidderSmartAdserver BidderName = "smartadserver"
- BidderSmartRTB BidderName = "smartrtb"
- BidderSmartyAds BidderName = "smartyads"
- BidderSomoaudience BidderName = "somoaudience"
- BidderSonobi BidderName = "sonobi"
- BidderSovrn BidderName = "sovrn"
- BidderSpotX BidderName = "spotx"
- BidderSynacormedia BidderName = "synacormedia"
- BidderTappx BidderName = "tappx"
- BidderTelaria BidderName = "telaria"
- BidderTriplelift BidderName = "triplelift"
- BidderTripleliftNative BidderName = "triplelift_native"
- BidderTrustX BidderName = "trustx"
- BidderUcfunnel BidderName = "ucfunnel"
- BidderUnicorn BidderName = "unicorn"
- BidderUnruly BidderName = "unruly"
- BidderValueImpression BidderName = "valueimpression"
- BidderVASTBidder BidderName = "vastbidder"
- BidderVerizonMedia BidderName = "verizonmedia"
- BidderVisx BidderName = "visx"
- BidderVrtcal BidderName = "vrtcal"
- BidderYeahmobi BidderName = "yeahmobi"
- BidderYieldlab BidderName = "yieldlab"
- BidderYieldmo BidderName = "yieldmo"
- BidderYieldone BidderName = "yieldone"
- BidderZeroClickFraud BidderName = "zeroclickfraud"
+ Bidder33Across BidderName = "33across"
+ BidderAcuityAds BidderName = "acuityads"
+ BidderAdagio BidderName = "adagio"
+ BidderAdf BidderName = "adf"
+ BidderAdform BidderName = "adform"
+ BidderAdgeneration BidderName = "adgeneration"
+ BidderAdhese BidderName = "adhese"
+ BidderAdkernel BidderName = "adkernel"
+ BidderAdkernelAdn BidderName = "adkernelAdn"
+ BidderAdman BidderName = "adman"
+ BidderAdmixer BidderName = "admixer"
+ BidderAdOcean BidderName = "adocean"
+ BidderAdoppler BidderName = "adoppler"
+ BidderAdot BidderName = "adot"
+ BidderAdpone BidderName = "adpone"
+ BidderAdprime BidderName = "adprime"
+ BidderAdtarget BidderName = "adtarget"
+ BidderAdtelligent BidderName = "adtelligent"
+ BidderAdvangelists BidderName = "advangelists"
+ BidderAdxcg BidderName = "adxcg"
+ BidderAdyoulike BidderName = "adyoulike"
+ BidderAJA BidderName = "aja"
+ BidderAlgorix BidderName = "algorix"
+ BidderAMX BidderName = "amx"
+ BidderApplogy BidderName = "applogy"
+ BidderAppnexus BidderName = "appnexus"
+ BidderAudienceNetwork BidderName = "audienceNetwork"
+ BidderAvocet BidderName = "avocet"
+ BidderAxonix BidderName = "axonix"
+ BidderBeachfront BidderName = "beachfront"
+ BidderBeintoo BidderName = "beintoo"
+ BidderBetween BidderName = "between"
+ BidderBidmachine BidderName = "bidmachine"
+ BidderBidmyadz BidderName = "bidmyadz"
+ BidderBidsCube BidderName = "bidscube"
+ BidderBmtm BidderName = "bmtm"
+ BidderBrightroll BidderName = "brightroll"
+ BidderColossus BidderName = "colossus"
+ BidderConnectAd BidderName = "connectad"
+ BidderConsumable BidderName = "consumable"
+ BidderConversant BidderName = "conversant"
+ BidderCpmstar BidderName = "cpmstar"
+ BidderCriteo BidderName = "criteo"
+ BidderDatablocks BidderName = "datablocks"
+ BidderDmx BidderName = "dmx"
+ BidderDecenterAds BidderName = "decenterads"
+ BidderDeepintent BidderName = "deepintent"
+ BidderEmxDigital BidderName = "emx_digital"
+ BidderEngageBDR BidderName = "engagebdr"
+ BidderEPlanning BidderName = "eplanning"
+ BidderEpom BidderName = "epom"
+ BidderEVolution BidderName = "e_volution"
+ BidderGamma BidderName = "gamma"
+ BidderGamoshi BidderName = "gamoshi"
+ BidderGrid BidderName = "grid"
+ BidderGumGum BidderName = "gumgum"
+ BidderImprovedigital BidderName = "improvedigital"
+ BidderInMobi BidderName = "inmobi"
+ BidderInteractiveoffers BidderName = "interactiveoffers"
+ BidderInvibes BidderName = "invibes"
+ BidderIx BidderName = "ix"
+ BidderJixie BidderName = "jixie"
+ BidderKayzen BidderName = "kayzen"
+ BidderKidoz BidderName = "kidoz"
+ BidderKrushmedia BidderName = "krushmedia"
+ BidderKubient BidderName = "kubient"
+ BidderLockerDome BidderName = "lockerdome"
+ BidderLogicad BidderName = "logicad"
+ BidderLunaMedia BidderName = "lunamedia"
+ BidderSaLunaMedia BidderName = "sa_lunamedia"
+ BidderMadvertise BidderName = "madvertise"
+ BidderMarsmedia BidderName = "marsmedia"
+ BidderMediafuse BidderName = "mediafuse"
+ BidderMgid BidderName = "mgid"
+ BidderMobfoxpb BidderName = "mobfoxpb"
+ BidderMobileFuse BidderName = "mobilefuse"
+ BidderNanoInteractive BidderName = "nanointeractive"
+ BidderNinthDecimal BidderName = "ninthdecimal"
+ BidderNoBid BidderName = "nobid"
+ BidderOneTag BidderName = "onetag"
+ BidderOpenx BidderName = "openx"
+ BidderOperaads BidderName = "operaads"
+ BidderOrbidder BidderName = "orbidder"
+ BidderOutbrain BidderName = "outbrain"
+ BidderPangle BidderName = "pangle"
+ BidderPubmatic BidderName = "pubmatic"
+ BidderPubnative BidderName = "pubnative"
+ BidderPulsepoint BidderName = "pulsepoint"
+ BidderRevcontent BidderName = "revcontent"
+ BidderRhythmone BidderName = "rhythmone"
+ BidderRTBHouse BidderName = "rtbhouse"
+ BidderRubicon BidderName = "rubicon"
+ BidderSharethrough BidderName = "sharethrough"
+ BidderSilverMob BidderName = "silvermob"
+ BidderSmaato BidderName = "smaato"
+ BidderSmartAdserver BidderName = "smartadserver"
+ BidderSmartRTB BidderName = "smartrtb"
+ BidderSmartyAds BidderName = "smartyads"
+ BidderSmileWanted BidderName = "smilewanted"
+ BidderSomoaudience BidderName = "somoaudience"
+ BidderSonobi BidderName = "sonobi"
+ BidderSovrn BidderName = "sovrn"
+ BidderSpotX BidderName = "spotx"
+ BidderSynacormedia BidderName = "synacormedia"
+ BidderTappx BidderName = "tappx"
+ BidderTelaria BidderName = "telaria"
+ BidderTriplelift BidderName = "triplelift"
+ BidderTripleliftNative BidderName = "triplelift_native"
+ BidderTrustX BidderName = "trustx"
+ BidderUcfunnel BidderName = "ucfunnel"
+ BidderUnicorn BidderName = "unicorn"
+ BidderUnruly BidderName = "unruly"
+ BidderValueImpression BidderName = "valueimpression"
+ BidderVASTBidder BidderName = "vastbidder"
+ BidderVerizonMedia BidderName = "verizonmedia"
+ BidderVisx BidderName = "visx"
+ BidderViewdeos BidderName = "viewdeos"
+ BidderVrtcal BidderName = "vrtcal"
+ BidderYeahmobi BidderName = "yeahmobi"
+ BidderYieldlab BidderName = "yieldlab"
+ BidderYieldmo BidderName = "yieldmo"
+ BidderYieldone BidderName = "yieldone"
+ BidderZeroClickFraud BidderName = "zeroclickfraud"
)
// CoreBidderNames returns a slice of all core bidders.
@@ -189,6 +203,8 @@ func CoreBidderNames() []BidderName {
return []BidderName{
Bidder33Across,
BidderAcuityAds,
+ BidderAdagio,
+ BidderAdf,
BidderAdform,
BidderAdgeneration,
BidderAdhese,
@@ -207,15 +223,20 @@ func CoreBidderNames() []BidderName {
BidderAdxcg,
BidderAdyoulike,
BidderAJA,
+ BidderAlgorix,
BidderAMX,
BidderApplogy,
BidderAppnexus,
BidderAudienceNetwork,
BidderAvocet,
+ BidderAxonix,
BidderBeachfront,
BidderBeintoo,
BidderBetween,
BidderBidmachine,
+ BidderBidmyadz,
+ BidderBidsCube,
+ BidderBmtm,
BidderBrightroll,
BidderColossus,
BidderConnectAd,
@@ -231,22 +252,26 @@ func CoreBidderNames() []BidderName {
BidderEngageBDR,
BidderEPlanning,
BidderEpom,
+ BidderEVolution,
BidderGamma,
BidderGamoshi,
BidderGrid,
BidderGumGum,
BidderImprovedigital,
BidderInMobi,
+ BidderInteractiveoffers,
BidderInvibes,
BidderIx,
BidderJixie,
+ BidderKayzen,
BidderKidoz,
BidderKrushmedia,
BidderKubient,
- BidderLifestreet,
BidderLockerDome,
BidderLogicad,
BidderLunaMedia,
+ BidderSaLunaMedia,
+ BidderMadvertise,
BidderMarsmedia,
BidderMediafuse,
BidderMgid,
@@ -257,6 +282,7 @@ func CoreBidderNames() []BidderName {
BidderNoBid,
BidderOneTag,
BidderOpenx,
+ BidderOperaads,
BidderOrbidder,
BidderOutbrain,
BidderPangle,
@@ -273,6 +299,7 @@ func CoreBidderNames() []BidderName {
BidderSmartAdserver,
BidderSmartRTB,
BidderSmartyAds,
+ BidderSmileWanted,
BidderSomoaudience,
BidderSonobi,
BidderSovrn,
@@ -289,6 +316,7 @@ func CoreBidderNames() []BidderName {
BidderValueImpression,
BidderVASTBidder,
BidderVerizonMedia,
+ BidderViewdeos,
BidderVisx,
BidderVrtcal,
BidderYeahmobi,
diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go
index cc06f3806cf..1e8605562d2 100644
--- a/openrtb_ext/device.go
+++ b/openrtb_ext/device.go
@@ -70,8 +70,8 @@ type ExtDevicePrebid struct {
// ExtDeviceInt defines the contract for bidrequest.device.ext.prebid.interstitial
type ExtDeviceInt struct {
- MinWidthPerc uint64 `json:"minwidtheperc"`
- MinHeightPerc uint64 `json:"minheightperc"`
+ MinWidthPerc int64 `json:"minwidtheperc"`
+ MinHeightPerc int64 `json:"minheightperc"`
}
func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error {
@@ -85,7 +85,7 @@ func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error {
if err != nil || perc < 0 || perc > 100 {
return &errortypes.BadInput{Message: "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100"}
}
- edi.MinWidthPerc = uint64(perc)
+ edi.MinWidthPerc = int64(perc)
}
if value, dataType, _, _ := jsonparser.Get(b, "minheightperc"); dataType != jsonparser.Number {
return &errortypes.BadInput{Message: "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100"}
@@ -94,7 +94,7 @@ func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error {
if err != nil || perc < 0 || perc > 100 {
return &errortypes.BadInput{Message: "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100"}
}
- edi.MinHeightPerc = uint64(perc)
+ edi.MinHeightPerc = int64(perc)
}
return nil
}
diff --git a/openrtb_ext/imp_adf.go b/openrtb_ext/imp_adf.go
new file mode 100644
index 00000000000..8ef02a460b2
--- /dev/null
+++ b/openrtb_ext/imp_adf.go
@@ -0,0 +1,9 @@
+package openrtb_ext
+
+import (
+ "encoding/json"
+)
+
+type ExtImpAdf struct {
+ MasterTagID json.Number `json:"mid"`
+}
diff --git a/openrtb_ext/imp_algorix.go b/openrtb_ext/imp_algorix.go
new file mode 100644
index 00000000000..e63a45ee11e
--- /dev/null
+++ b/openrtb_ext/imp_algorix.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+// ExtImpAlgoriX defines the contract for bidrequest.imp[i].ext.algorix
+type ExtImpAlgorix struct {
+ Sid string `json:"sid"`
+ Token string `json:"token"`
+}
diff --git a/openrtb_ext/imp_appnexus.go b/openrtb_ext/imp_appnexus.go
index 9ef69d06458..83d762b3c19 100644
--- a/openrtb_ext/imp_appnexus.go
+++ b/openrtb_ext/imp_appnexus.go
@@ -17,6 +17,7 @@ type ExtImpAppnexus struct {
UsePmtRule *bool `json:"use_pmt_rule"`
// At this time we do no processing on the private sizes, so just leaving it as a JSON blob.
PrivateSizes json.RawMessage `json:"private_sizes"`
+ AdPodId bool `json:"generate_ad_pod_id"`
}
// ExtImpAppnexusKeyVal defines the contract for bidrequest.imp[i].ext.appnexus.keywords[i]
diff --git a/openrtb_ext/imp_axonix.go b/openrtb_ext/imp_axonix.go
new file mode 100644
index 00000000000..7dd9f68418d
--- /dev/null
+++ b/openrtb_ext/imp_axonix.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpAxonix struct {
+ SupplyId string `json:"supplyId"`
+}
diff --git a/openrtb_ext/imp_between.go b/openrtb_ext/imp_between.go
index c868baeb9da..9fe912a8618 100644
--- a/openrtb_ext/imp_between.go
+++ b/openrtb_ext/imp_between.go
@@ -1,8 +1,6 @@
package openrtb_ext
type ExtImpBetween struct {
- Host string `json:"host"`
- PublisherID string `json:"publisher_id"`
- BidFloor float64 `json:"bid_floor"`
- BidFloorCur string `json:"bid_floor_cur"`
+ Host string `json:"host"`
+ PublisherID string `json:"publisher_id"`
}
diff --git a/openrtb_ext/imp_bidscube.go b/openrtb_ext/imp_bidscube.go
new file mode 100644
index 00000000000..638cad760aa
--- /dev/null
+++ b/openrtb_ext/imp_bidscube.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpBidsCube struct {
+ PlacementID string `json:"placementId"`
+}
diff --git a/openrtb_ext/imp_bmtm.go b/openrtb_ext/imp_bmtm.go
new file mode 100644
index 00000000000..a9fd7a52c5e
--- /dev/null
+++ b/openrtb_ext/imp_bmtm.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ImpExtBmtm struct {
+ PlacementID int `json:"placement_id"`
+}
diff --git a/openrtb_ext/imp_interactiveoffers.go b/openrtb_ext/imp_interactiveoffers.go
new file mode 100644
index 00000000000..2f8fdf0324e
--- /dev/null
+++ b/openrtb_ext/imp_interactiveoffers.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpInteractiveoffers struct {
+ PubID int `json:"pubid"`
+}
diff --git a/openrtb_ext/imp_kayzen.go b/openrtb_ext/imp_kayzen.go
new file mode 100644
index 00000000000..35dece64036
--- /dev/null
+++ b/openrtb_ext/imp_kayzen.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+// ExtKayzen defines the contract for bidrequest.imp[i].ext.kayzen
+type ExtKayzen struct {
+ Zone string `json:"zone"`
+ Exchange string `json:"exchange"`
+}
diff --git a/openrtb_ext/imp_madvertise.go b/openrtb_ext/imp_madvertise.go
new file mode 100644
index 00000000000..30bd88dbf51
--- /dev/null
+++ b/openrtb_ext/imp_madvertise.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+// ExtImpMadvertise defines the contract for bidrequest.imp[i].ext.madvertise
+type ExtImpMadvertise struct {
+ ZoneID string `json:"zoneId"`
+}
diff --git a/openrtb_ext/imp_operaads.go b/openrtb_ext/imp_operaads.go
new file mode 100644
index 00000000000..99ccd7c431b
--- /dev/null
+++ b/openrtb_ext/imp_operaads.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+type ImpExtOperaads struct {
+ PlacementID string `json:"placementId"`
+ EndpointID string `json:"endpointId"`
+ PublisherID string `json:"publisherId"`
+}
diff --git a/openrtb_ext/imp_pangle.go b/openrtb_ext/imp_pangle.go
index cc32dcf1d01..cf3b7e74f41 100644
--- a/openrtb_ext/imp_pangle.go
+++ b/openrtb_ext/imp_pangle.go
@@ -1,5 +1,7 @@
package openrtb_ext
type ImpExtPangle struct {
- Token string `json:"token"`
+ Token string `json:"token"`
+ AppID string `json:"appid,omitempty"`
+ PlacementID string `json:"placementid,omitempty"`
}
diff --git a/openrtb_ext/imp_sa_lunamedia.go b/openrtb_ext/imp_sa_lunamedia.go
new file mode 100644
index 00000000000..cb99b0ac561
--- /dev/null
+++ b/openrtb_ext/imp_sa_lunamedia.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ExtImpSaLunamedia struct {
+ Key string `json:"key"`
+ Type string `json:"type,omitempty"`
+}
diff --git a/openrtb_ext/imp_sharethrough.go b/openrtb_ext/imp_sharethrough.go
index 3f3780334e6..7c3f1f6781d 100644
--- a/openrtb_ext/imp_sharethrough.go
+++ b/openrtb_ext/imp_sharethrough.go
@@ -1,13 +1,18 @@
package openrtb_ext
-type ExtImpSharethrough struct {
- Pkey string `json:"pkey"`
- Iframe bool `json:"iframe"`
- IframeSize []int `json:"iframeSize"`
- BidFloor float64 `json:"bidfloor"`
+type ExtData struct {
+ PBAdSlot string `json:"pbadslot"`
}
// ExtImpSharethrough defines the contract for bidrequest.imp[i].ext.sharethrough
+type ExtImpSharethrough struct {
+ Pkey string `json:"pkey"`
+ Iframe bool `json:"iframe"`
+ IframeSize []int `json:"iframeSize"`
+ BidFloor float64 `json:"bidfloor"`
+ Data *ExtData `json:"data,omitempty"`
+}
+
type ExtImpSharethroughResponse struct {
AdServerRequestID string `json:"adserverRequestId"`
BidID string `json:"bidId"`
diff --git a/openrtb_ext/imp_smaato.go b/openrtb_ext/imp_smaato.go
index 10de97fb017..14dcb73bdf3 100644
--- a/openrtb_ext/imp_smaato.go
+++ b/openrtb_ext/imp_smaato.go
@@ -1,9 +1,12 @@
package openrtb_ext
// ExtImpSmaato defines the contract for bidrequest.imp[i].ext.smaato
-// PublisherId and AdSpaceId are mandatory parameters, others are optional parameters
+// PublisherId and AdSpaceId are mandatory parameters for non adpod (long-form video) requests, others are optional parameters
+// PublisherId and AdBreakId are mandatory parameters for adpod (long-form video) requests, others are optional parameters
// AdSpaceId is identifier for specific ad placement or ad tag
+// AdBreakId is identifier for specific ad placement or ad tag
type ExtImpSmaato struct {
PublisherID string `json:"publisherId"`
AdSpaceID string `json:"adspaceId"`
+ AdBreakID string `json:"adbreakId"`
}
diff --git a/openrtb_ext/imp_tappx.go b/openrtb_ext/imp_tappx.go
index c1ca77bf632..ca8693abd9f 100644
--- a/openrtb_ext/imp_tappx.go
+++ b/openrtb_ext/imp_tappx.go
@@ -1,8 +1,11 @@
package openrtb_ext
type ExtImpTappx struct {
- Host string `json:"host"`
- TappxKey string `json:"tappxkey"`
- Endpoint string `json:"endpoint"`
- BidFloor float64 `json:"bidfloor,omitempty"`
+ Host string `json:"host"`
+ TappxKey string `json:"tappxkey"`
+ Endpoint string `json:"endpoint"`
+ BidFloor float64 `json:"bidfloor,omitempty"`
+ Mktag string `json:"mktag,omitempty"`
+ Bcid []string `json:"bcid,omitempty"`
+ Bcrid []string `json:"bcrid,omitempty"`
}
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index 8d6e6d13546..24766ab1603 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -14,6 +14,9 @@ const FirstPartyDataContextExtKey = "context"
// SKAdNExtKey defines the field name within request.ext reserved for Apple's SKAdNetwork.
const SKAdNExtKey = "skadn"
+// NativeExchangeSpecificLowerBound defines the lower threshold of exchange specific types for native ads. There is no upper bound.
+const NativeExchangeSpecificLowerBound = 500
+
const MaxDecimalFigures int = 15
// ExtRequest defines the contract for bidrequest.ext
@@ -43,6 +46,13 @@ type ExtRequestPrebid struct {
// Macros specifies list of custom macros along with the values. This is used while forming
// the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding
Macros map[string]string `json:"macros,omitempty"`
+
+ CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"`
+}
+
+type ExtRequestCurrency struct {
+ ConversionRates map[string]map[string]float64 `json:"rates"`
+ UsePBSRates *bool `json:"usepbsrates"`
}
// ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains
diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go
new file mode 100644
index 00000000000..276f8f5eebe
--- /dev/null
+++ b/openrtb_ext/request_wrapper.go
@@ -0,0 +1,787 @@
+package openrtb_ext
+
+import (
+ "encoding/json"
+ "errors"
+
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+)
+
+// RequestWrapper wraps the OpenRTB request to provide a storage location for unmarshalled ext fields, so they
+// will not need to be unmarshalled multiple times.
+//
+// To start with, the wrapper can be created for a request 'req' via:
+// reqWrapper := openrtb_ext.RequestWrapper{BidRequest: req}
+//
+// In order to access an object's ext field, fetch it via:
+// userExt, err := reqWrapper.GetUserExt()
+// or other Get method as appropriate.
+//
+// To read or write values, use the Ext objects Get and Set methods. If you need to write to a field that has its own Set
+// method, use that to set the value rather than using SetExt() with that change done in the map; when rewritting the
+// ext JSON the code will overwrite the the values in the map with the values stored in the seperate fields.
+//
+// userPrebid := userExt.GetPrebid()
+// userExt.SetConsent(consentString)
+//
+// The GetExt() and SetExt() should only be used to access fields that have not already been resolved in the object.
+// Using SetExt() at all is a strong hint that the ext object should be extended to support the new fields being set
+// in the map.
+//
+// NOTE: The RequestWrapper methods (particularly the ones calling (un)Marshal are not thread safe)
+
+type RequestWrapper struct {
+ *openrtb2.BidRequest
+ userExt *UserExt
+ deviceExt *DeviceExt
+ requestExt *RequestExt
+ appExt *AppExt
+ regExt *RegExt
+ siteExt *SiteExt
+}
+
+func (rw *RequestWrapper) GetUserExt() (*UserExt, error) {
+ if rw.userExt != nil {
+ return rw.userExt, nil
+ }
+ rw.userExt = &UserExt{}
+ if rw.BidRequest == nil || rw.User == nil || rw.User.Ext == nil {
+ return rw.userExt, rw.userExt.unmarshal(json.RawMessage{})
+ }
+
+ return rw.userExt, rw.userExt.unmarshal(rw.User.Ext)
+}
+
+func (rw *RequestWrapper) GetDeviceExt() (*DeviceExt, error) {
+ if rw.deviceExt != nil {
+ return rw.deviceExt, nil
+ }
+ rw.deviceExt = &DeviceExt{}
+ if rw.BidRequest == nil || rw.Device == nil || rw.Device.Ext == nil {
+ return rw.deviceExt, rw.deviceExt.unmarshal(json.RawMessage{})
+ }
+ return rw.deviceExt, rw.deviceExt.unmarshal(rw.Device.Ext)
+}
+
+func (rw *RequestWrapper) GetRequestExt() (*RequestExt, error) {
+ if rw.requestExt != nil {
+ return rw.requestExt, nil
+ }
+ rw.requestExt = &RequestExt{}
+ if rw.BidRequest == nil || rw.Ext == nil {
+ return rw.requestExt, rw.requestExt.unmarshal(json.RawMessage{})
+ }
+ return rw.requestExt, rw.requestExt.unmarshal(rw.Ext)
+}
+
+func (rw *RequestWrapper) GetAppExt() (*AppExt, error) {
+ if rw.appExt != nil {
+ return rw.appExt, nil
+ }
+ rw.appExt = &AppExt{}
+ if rw.BidRequest == nil || rw.App == nil || rw.App.Ext == nil {
+ return rw.appExt, rw.appExt.unmarshal(json.RawMessage{})
+ }
+ return rw.appExt, rw.appExt.unmarshal(rw.App.Ext)
+}
+
+func (rw *RequestWrapper) GetRegExt() (*RegExt, error) {
+ if rw.regExt != nil {
+ return rw.regExt, nil
+ }
+ rw.regExt = &RegExt{}
+ if rw.BidRequest == nil || rw.Regs == nil || rw.Regs.Ext == nil {
+ return rw.regExt, rw.regExt.unmarshal(json.RawMessage{})
+ }
+ return rw.regExt, rw.regExt.unmarshal(rw.Regs.Ext)
+}
+
+func (rw *RequestWrapper) GetSiteExt() (*SiteExt, error) {
+ if rw.siteExt != nil {
+ return rw.siteExt, nil
+ }
+ rw.siteExt = &SiteExt{}
+ if rw.BidRequest == nil || rw.Site == nil || rw.Site.Ext == nil {
+ return rw.siteExt, rw.siteExt.unmarshal(json.RawMessage{})
+ }
+ return rw.siteExt, rw.siteExt.unmarshal(rw.Site.Ext)
+}
+
+func (rw *RequestWrapper) RebuildRequest() error {
+ if rw.BidRequest == nil {
+ return errors.New("Requestwrapper Sync called on a nil BidRequest")
+ }
+
+ if err := rw.rebuildUserExt(); err != nil {
+ return err
+ }
+ if err := rw.rebuildDeviceExt(); err != nil {
+ return err
+ }
+ if err := rw.rebuildRequestExt(); err != nil {
+ return err
+ }
+ if err := rw.rebuildAppExt(); err != nil {
+ return err
+ }
+ if err := rw.rebuildRegExt(); err != nil {
+ return err
+ }
+ if err := rw.rebuildSiteExt(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (rw *RequestWrapper) rebuildUserExt() error {
+ if rw.BidRequest.User == nil && rw.userExt != nil && rw.userExt.Dirty() {
+ rw.User = &openrtb2.User{}
+ }
+ if rw.userExt != nil && rw.userExt.Dirty() {
+ userJson, err := rw.userExt.marshal()
+ if err != nil {
+ return err
+ }
+ rw.User.Ext = userJson
+ }
+ return nil
+}
+
+func (rw *RequestWrapper) rebuildDeviceExt() error {
+ if rw.Device == nil && rw.deviceExt != nil && rw.deviceExt.Dirty() {
+ rw.Device = &openrtb2.Device{}
+ }
+ if rw.deviceExt != nil && rw.deviceExt.Dirty() {
+ deviceJson, err := rw.deviceExt.marshal()
+ if err != nil {
+ return err
+ }
+ rw.Device.Ext = deviceJson
+ }
+ return nil
+}
+
+func (rw *RequestWrapper) rebuildRequestExt() error {
+ if rw.requestExt != nil && rw.requestExt.Dirty() {
+ requestJson, err := rw.requestExt.marshal()
+ if err != nil {
+ return err
+ }
+ rw.Ext = requestJson
+ }
+ return nil
+}
+
+func (rw *RequestWrapper) rebuildAppExt() error {
+ if rw.App == nil && rw.appExt != nil && rw.appExt.Dirty() {
+ rw.App = &openrtb2.App{}
+ }
+ if rw.appExt != nil && rw.appExt.Dirty() {
+ appJson, err := rw.appExt.marshal()
+ if err != nil {
+ return err
+ }
+ rw.App.Ext = appJson
+ }
+ return nil
+}
+
+func (rw *RequestWrapper) rebuildRegExt() error {
+ if rw.Regs == nil && rw.regExt != nil && rw.regExt.Dirty() {
+ rw.Regs = &openrtb2.Regs{}
+ }
+ if rw.regExt != nil && rw.regExt.Dirty() {
+ regsJson, err := rw.regExt.marshal()
+ if err != nil {
+ return err
+ }
+ rw.Regs.Ext = regsJson
+ }
+ return nil
+}
+
+func (rw *RequestWrapper) rebuildSiteExt() error {
+ if rw.Site == nil && rw.siteExt != nil && rw.siteExt.Dirty() {
+ rw.Site = &openrtb2.Site{}
+ }
+ if rw.siteExt != nil && rw.siteExt.Dirty() {
+ siteJson, err := rw.siteExt.marshal()
+ if err != nil {
+ return err
+ }
+ rw.Regs.Ext = siteJson
+ }
+ return nil
+}
+
+// ---------------------------------------------------------------
+// UserExt provides an interface for request.user.ext
+// ---------------------------------------------------------------
+
+type UserExt struct {
+ ext map[string]json.RawMessage
+ extDirty bool
+ consent *string
+ consentDirty bool
+ prebid *ExtUserPrebid
+ prebidDirty bool
+ eids *[]ExtUserEid
+ eidsDirty bool
+}
+
+func (ue *UserExt) unmarshal(extJson json.RawMessage) error {
+ if len(ue.ext) != 0 || ue.Dirty() {
+ return nil
+ }
+ ue.ext = make(map[string]json.RawMessage)
+ if len(extJson) == 0 {
+ return nil
+ }
+
+ if err := json.Unmarshal(extJson, &ue.ext); err != nil {
+ return err
+ }
+
+ consentJson, hasConsent := ue.ext["consent"]
+ if hasConsent {
+ if err := json.Unmarshal(consentJson, &ue.consent); err != nil {
+ return err
+ }
+ }
+
+ prebidJson, hasPrebid := ue.ext["prebid"]
+ if hasPrebid {
+ ue.prebid = &ExtUserPrebid{}
+ if err := json.Unmarshal(prebidJson, ue.prebid); err != nil {
+ return err
+ }
+ }
+
+ eidsJson, hasEids := ue.ext["eids"]
+ if hasEids {
+ ue.eids = &[]ExtUserEid{}
+ if err := json.Unmarshal(eidsJson, ue.eids); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (ue *UserExt) marshal() (json.RawMessage, error) {
+ if ue.consentDirty {
+ consentJson, err := json.Marshal(ue.consent)
+ if err != nil {
+ return nil, err
+ }
+ if len(consentJson) > 2 {
+ ue.ext["consent"] = json.RawMessage(consentJson)
+ } else {
+ delete(ue.ext, "consent")
+ }
+ ue.consentDirty = false
+ }
+
+ if ue.prebidDirty {
+ prebidJson, err := json.Marshal(ue.prebid)
+ if err != nil {
+ return nil, err
+ }
+ if len(prebidJson) > 2 {
+ ue.ext["prebid"] = json.RawMessage(prebidJson)
+ } else {
+ delete(ue.ext, "prebid")
+ }
+ ue.prebidDirty = false
+ }
+
+ if ue.eidsDirty {
+ if len(*ue.eids) > 0 {
+ eidsJson, err := json.Marshal(ue.eids)
+ if err != nil {
+ return nil, err
+ }
+ ue.ext["eids"] = json.RawMessage(eidsJson)
+ } else {
+ delete(ue.ext, "eids")
+ }
+ ue.eidsDirty = false
+ }
+
+ ue.extDirty = false
+ if len(ue.ext) == 0 {
+ return nil, nil
+ }
+ return json.Marshal(ue.ext)
+
+}
+
+func (ue *UserExt) Dirty() bool {
+ return ue.extDirty || ue.eidsDirty || ue.prebidDirty || ue.consentDirty
+}
+
+func (ue *UserExt) GetExt() map[string]json.RawMessage {
+ ext := make(map[string]json.RawMessage)
+ for k, v := range ue.ext {
+ ext[k] = v
+ }
+ return ext
+}
+
+func (ue *UserExt) SetExt(ext map[string]json.RawMessage) {
+ ue.ext = ext
+ ue.extDirty = true
+}
+
+func (ue *UserExt) GetConsent() *string {
+ if ue.consent == nil {
+ return nil
+ }
+ consent := *ue.consent
+ return &consent
+}
+
+func (ue *UserExt) SetConsent(consent *string) {
+ ue.consent = consent
+ ue.consentDirty = true
+}
+
+func (ue *UserExt) GetPrebid() *ExtUserPrebid {
+ if ue.prebid == nil {
+ return nil
+ }
+ prebid := *ue.prebid
+ return &prebid
+}
+
+func (ue *UserExt) SetPrebid(prebid *ExtUserPrebid) {
+ ue.prebid = prebid
+ ue.prebidDirty = true
+}
+
+func (ue *UserExt) GetEid() *[]ExtUserEid {
+ if ue.eids == nil {
+ return nil
+ }
+ eids := *ue.eids
+ return &eids
+}
+
+func (ue *UserExt) SetEid(eid *[]ExtUserEid) {
+ ue.eids = eid
+ ue.eidsDirty = true
+}
+
+// ---------------------------------------------------------------
+// RequestExt provides an interface for request.ext
+// ---------------------------------------------------------------
+
+type RequestExt struct {
+ ext map[string]json.RawMessage
+ extDirty bool
+ prebid *ExtRequestPrebid
+ prebidDirty bool
+}
+
+func (re *RequestExt) unmarshal(extJson json.RawMessage) error {
+ if len(re.ext) != 0 || re.Dirty() {
+ return nil
+ }
+ re.ext = make(map[string]json.RawMessage)
+ if len(extJson) == 0 {
+ return nil
+ }
+ err := json.Unmarshal(extJson, &re.ext)
+ if err != nil {
+ return err
+ }
+ prebidJson, hasPrebid := re.ext["prebid"]
+ if hasPrebid {
+ re.prebid = &ExtRequestPrebid{}
+ err = json.Unmarshal(prebidJson, re.prebid)
+ }
+
+ return err
+}
+
+func (re *RequestExt) marshal() (json.RawMessage, error) {
+ if re.prebidDirty {
+ prebidJson, err := json.Marshal(re.prebid)
+ if err != nil {
+ return nil, err
+ }
+ if len(prebidJson) > 2 {
+ re.ext["prebid"] = json.RawMessage(prebidJson)
+ } else {
+ delete(re.ext, "prebid")
+ }
+ re.prebidDirty = false
+ }
+
+ re.extDirty = false
+ if len(re.ext) == 0 {
+ return nil, nil
+ }
+ return json.Marshal(re.ext)
+}
+
+func (re *RequestExt) Dirty() bool {
+ return re.extDirty || re.prebidDirty
+}
+
+func (re *RequestExt) GetExt() map[string]json.RawMessage {
+ ext := make(map[string]json.RawMessage)
+ for k, v := range re.ext {
+ ext[k] = v
+ }
+ return ext
+}
+
+func (re *RequestExt) SetExt(ext map[string]json.RawMessage) {
+ re.ext = ext
+ re.extDirty = true
+}
+
+func (re *RequestExt) GetPrebid() *ExtRequestPrebid {
+ if re.prebid == nil {
+ return nil
+ }
+ prebid := *re.prebid
+ return &prebid
+}
+
+func (re *RequestExt) SetPrebid(prebid *ExtRequestPrebid) {
+ re.prebid = prebid
+ re.prebidDirty = true
+}
+
+// ---------------------------------------------------------------
+// DeviceExt provides an interface for request.device.ext
+// ---------------------------------------------------------------
+// NOTE: openrtb_ext/device.go:ParseDeviceExtATTS() uses ext.atts, as read only, via jsonparser, only for IOS.
+// Doesn't seem like we will see any performance savings by parsing atts at this point, and as it is read only,
+// we don't need to worry about write conflicts. Note here in case additional uses of atts evolve as things progress.
+// ---------------------------------------------------------------
+
+type DeviceExt struct {
+ ext map[string]json.RawMessage
+ extDirty bool
+ prebid *ExtDevicePrebid
+ prebidDirty bool
+}
+
+func (de *DeviceExt) unmarshal(extJson json.RawMessage) error {
+ if len(de.ext) != 0 || de.Dirty() {
+ return nil
+ }
+ de.ext = make(map[string]json.RawMessage)
+ if len(extJson) == 0 {
+ return nil
+ }
+ err := json.Unmarshal(extJson, &de.ext)
+ if err != nil {
+ return err
+ }
+ prebidJson, hasPrebid := de.ext["prebid"]
+ if hasPrebid {
+ de.prebid = &ExtDevicePrebid{}
+ err = json.Unmarshal(prebidJson, de.prebid)
+ }
+
+ return err
+}
+
+func (de *DeviceExt) marshal() (json.RawMessage, error) {
+ if de.prebidDirty {
+ prebidJson, err := json.Marshal(de.prebid)
+ if err != nil {
+ return nil, err
+ }
+ if len(prebidJson) > 2 {
+ de.ext["prebid"] = json.RawMessage(prebidJson)
+ } else {
+ delete(de.ext, "prebid")
+ }
+ de.prebidDirty = false
+ }
+
+ de.extDirty = false
+ if len(de.ext) == 0 {
+ return nil, nil
+ }
+ return json.Marshal(de.ext)
+}
+
+func (de *DeviceExt) Dirty() bool {
+ return de.extDirty || de.prebidDirty
+}
+
+func (de *DeviceExt) GetExt() map[string]json.RawMessage {
+ ext := make(map[string]json.RawMessage)
+ for k, v := range de.ext {
+ ext[k] = v
+ }
+ return ext
+}
+
+func (de *DeviceExt) SetExt(ext map[string]json.RawMessage) {
+ de.ext = ext
+ de.extDirty = true
+}
+
+func (de *DeviceExt) GetPrebid() *ExtDevicePrebid {
+ if de.prebid == nil {
+ return nil
+ }
+ prebid := *de.prebid
+ return &prebid
+}
+
+func (de *DeviceExt) SetPrebid(prebid *ExtDevicePrebid) {
+ de.prebid = prebid
+ de.prebidDirty = true
+}
+
+// ---------------------------------------------------------------
+// AppExt provides an interface for request.app.ext
+// ---------------------------------------------------------------
+
+type AppExt struct {
+ ext map[string]json.RawMessage
+ extDirty bool
+ prebid *ExtAppPrebid
+ prebidDirty bool
+}
+
+func (ae *AppExt) unmarshal(extJson json.RawMessage) error {
+ if len(ae.ext) != 0 || ae.Dirty() {
+ return nil
+ }
+ ae.ext = make(map[string]json.RawMessage)
+ if len(extJson) == 0 {
+ return nil
+ }
+ err := json.Unmarshal(extJson, &ae.ext)
+ if err != nil {
+ return err
+ }
+ prebidJson, hasPrebid := ae.ext["prebid"]
+ if hasPrebid {
+ ae.prebid = &ExtAppPrebid{}
+ err = json.Unmarshal(prebidJson, ae.prebid)
+ }
+
+ return err
+}
+
+func (ae *AppExt) marshal() (json.RawMessage, error) {
+ if ae.prebidDirty {
+ prebidJson, err := json.Marshal(ae.prebid)
+ if err != nil {
+ return nil, err
+ }
+ if len(prebidJson) > 2 {
+ ae.ext["prebid"] = json.RawMessage(prebidJson)
+ } else {
+ delete(ae.ext, "prebid")
+ }
+ ae.prebidDirty = false
+ }
+
+ ae.extDirty = false
+ if len(ae.ext) == 0 {
+ return nil, nil
+ }
+ return json.Marshal(ae.ext)
+}
+
+func (ae *AppExt) Dirty() bool {
+ return ae.extDirty || ae.prebidDirty
+}
+
+func (ae *AppExt) GetExt() map[string]json.RawMessage {
+ ext := make(map[string]json.RawMessage)
+ for k, v := range ae.ext {
+ ext[k] = v
+ }
+ return ext
+}
+
+func (ae *AppExt) SetExt(ext map[string]json.RawMessage) {
+ ae.ext = ext
+ ae.extDirty = true
+}
+
+func (ae *AppExt) GetPrebid() *ExtAppPrebid {
+ if ae.prebid == nil {
+ return nil
+ }
+ prebid := *ae.prebid
+ return &prebid
+}
+
+func (ae *AppExt) SetPrebid(prebid *ExtAppPrebid) {
+ ae.prebid = prebid
+ ae.prebidDirty = true
+}
+
+// ---------------------------------------------------------------
+// RegExt provides an interface for request.regs.ext
+// ---------------------------------------------------------------
+
+type RegExt struct {
+ ext map[string]json.RawMessage
+ extDirty bool
+ usPrivacy string
+ usPrivacyDirty bool
+}
+
+func (re *RegExt) unmarshal(extJson json.RawMessage) error {
+ if len(re.ext) != 0 || re.Dirty() {
+ return nil
+ }
+ re.ext = make(map[string]json.RawMessage)
+ if len(extJson) == 0 {
+ return nil
+ }
+ err := json.Unmarshal(extJson, &re.ext)
+ if err != nil {
+ return err
+ }
+ uspJson, hasUsp := re.ext["us_privacy"]
+ if hasUsp {
+ err = json.Unmarshal(uspJson, &re.usPrivacy)
+ }
+
+ return err
+}
+
+func (re *RegExt) marshal() (json.RawMessage, error) {
+ if re.usPrivacyDirty {
+ if len(re.usPrivacy) > 0 {
+ rawjson, err := json.Marshal(re.usPrivacy)
+ if err != nil {
+ return nil, err
+ }
+ re.ext["us_privacy"] = rawjson
+ } else {
+ delete(re.ext, "us_privacy")
+ }
+ re.usPrivacyDirty = false
+ }
+
+ re.extDirty = false
+ if len(re.ext) == 0 {
+ return nil, nil
+ }
+ return json.Marshal(re.ext)
+}
+
+func (re *RegExt) Dirty() bool {
+ return re.extDirty || re.usPrivacyDirty
+}
+
+func (re *RegExt) GetExt() map[string]json.RawMessage {
+ ext := make(map[string]json.RawMessage)
+ for k, v := range re.ext {
+ ext[k] = v
+ }
+ return ext
+}
+
+func (re *RegExt) SetExt(ext map[string]json.RawMessage) {
+ re.ext = ext
+ re.extDirty = true
+}
+
+func (re *RegExt) GetUSPrivacy() string {
+ uSPrivacy := re.usPrivacy
+ return uSPrivacy
+}
+
+func (re *RegExt) SetUSPrivacy(uSPrivacy string) {
+ re.usPrivacy = uSPrivacy
+ re.usPrivacyDirty = true
+}
+
+// ---------------------------------------------------------------
+// SiteExt provides an interface for request.site.ext
+// ---------------------------------------------------------------
+
+type SiteExt struct {
+ ext map[string]json.RawMessage
+ extDirty bool
+ amp int8
+ ampDirty bool
+}
+
+func (se *SiteExt) unmarshal(extJson json.RawMessage) error {
+ if len(se.ext) != 0 || se.Dirty() {
+ return nil
+ }
+ se.ext = make(map[string]json.RawMessage)
+ if len(extJson) == 0 {
+ return nil
+ }
+ err := json.Unmarshal(extJson, &se.ext)
+ if err != nil {
+ return err
+ }
+ AmpJson, hasAmp := se.ext["amp"]
+ if hasAmp {
+ err = json.Unmarshal(AmpJson, &se.amp)
+ if err != nil {
+ err = errors.New(`request.site.ext.amp must be either 1, 0, or undefined`)
+ }
+ }
+
+ return err
+}
+
+func (se *SiteExt) marshal() (json.RawMessage, error) {
+ if se.ampDirty {
+ ampJson, err := json.Marshal(se.amp)
+ if err != nil {
+ return nil, err
+ }
+ if len(ampJson) > 2 {
+ se.ext["amp"] = json.RawMessage(ampJson)
+ } else {
+ delete(se.ext, "amp")
+ }
+ se.ampDirty = false
+ }
+
+ se.extDirty = false
+ if len(se.ext) == 0 {
+ return nil, nil
+ }
+ return json.Marshal(se.ext)
+}
+
+func (se *SiteExt) Dirty() bool {
+ return se.extDirty || se.ampDirty
+}
+
+func (se *SiteExt) GetExt() map[string]json.RawMessage {
+ ext := make(map[string]json.RawMessage)
+ for k, v := range se.ext {
+ ext[k] = v
+ }
+ return ext
+}
+
+func (se *SiteExt) SetExt(ext map[string]json.RawMessage) {
+ se.ext = ext
+ se.extDirty = true
+}
+
+func (se *SiteExt) GetAmp() int8 {
+ return se.amp
+}
+
+func (se *SiteExt) SetUSPrivacy(amp int8) {
+ se.amp = amp
+ se.ampDirty = true
+}
diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go
new file mode 100644
index 00000000000..06cad49aedf
--- /dev/null
+++ b/openrtb_ext/request_wrapper_test.go
@@ -0,0 +1,22 @@
+package openrtb_ext
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// Some minimal tests to get code coverage above 30%. The real tests are when other modules use these structures.
+
+func TestUserExt(t *testing.T) {
+ userExt := &UserExt{}
+
+ userExt.unmarshal(nil)
+ assert.Equal(t, false, userExt.Dirty(), "New UserExt should not be dirty.")
+ assert.Nil(t, userExt.GetConsent(), "Empty UserExt should have nil consent")
+
+ newConsent := "NewConsent"
+ userExt.SetConsent(&newConsent)
+ assert.Equal(t, "NewConsent", *userExt.GetConsent())
+
+}
diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go
index b83f82330db..d5e6ae678cc 100644
--- a/openrtb_ext/user.go
+++ b/openrtb_ext/user.go
@@ -10,11 +10,6 @@ type ExtUser struct {
Prebid *ExtUserPrebid `json:"prebid,omitempty"`
- // DigiTrust breaks the typical Prebid Server convention of namespacing "global" options inside "ext.prebid.*"
- // to match the recommendation from the broader digitrust community.
- // For more info, see: https://github.com/digi-trust/dt-cdn/wiki/OpenRTB-extension#openrtb-2x
- DigiTrust *ExtUserDigiTrust `json:"digitrust,omitempty"`
-
Eids []ExtUserEid `json:"eids,omitempty"`
}
@@ -23,14 +18,6 @@ type ExtUserPrebid struct {
BuyerUIDs map[string]string `json:"buyeruids,omitempty"`
}
-// ExtUserDigiTrust defines the contract for bidrequest.user.ext.digitrust
-// More info on DigiTrust can be found here: https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide
-type ExtUserDigiTrust struct {
- ID string `json:"id"` // Unique device identifier
- KeyV int `json:"keyv"` // Key version used to encrypt ID
- Pref int `json:"pref"` // User optout preference
-}
-
// ExtUserEid defines the contract for bidrequest.user.ext.eids
// Responsible for the Universal User ID support: establishing pseudonymous IDs for users.
// See https://github.com/prebid/Prebid.js/issues/3900 for details.
diff --git a/prebid_cache_client/prebid_cache_test.go b/prebid_cache_client/prebid_cache_test.go
index 2b33f2cebd1..65688789fd0 100644
--- a/prebid_cache_client/prebid_cache_test.go
+++ b/prebid_cache_client/prebid_cache_test.go
@@ -82,19 +82,10 @@ func TestPrebidClient(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer))
defer server.Close()
- cobj := make([]*CacheObject, 5)
+ cobj := make([]*CacheObject, 3)
- // example bids from lifestreet, facebook, and appnexus
+ // example bids
cobj[0] = &CacheObject{
- IsVideo: false,
- Value: &BidCache{
- Adm: "\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n
\n
\n\n\n\n\n
\n\n
\n\n\n\n\n\n\n\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
- NURL: "http://test.com/syspixel?__ads=ip8070-3PJ4q4QyZxnHE6woGe1sQ3&__adt=4122105428549383603&__ade=2&type=tracking&rqc=0w23qR-q7O7MsGkWlR9wOBm8qL7msKBtSKRJV3Pw0a0tZ47xJTnT2JwzqvXgrzPZOLZfI__68S9kCKELawQtZcO6kMyvlPM55uCaRZWng_j5btuPaEuXyA&pab=true",
- Width: 300,
- Height: 250,
- },
- }
- cobj[1] = &CacheObject{
IsVideo: false,
Value: &BidCache{
Adm: "{\"type\":\"ID\",\"bid_id\":\"8255649814109237089\",\"placement_id\":\"1995257847363113_1997038003851764\",\"resolved_placement_id\":\"1995257847363113_1997038003851764\",\"sdk_version\":\"4.25.0-appnexus.bidding\",\"device_id\":\"87ECBA49-908A-428F-9DE7-4B9CED4F486C\",\"template\":7,\"payload\":\"null\"}",
@@ -103,7 +94,7 @@ func TestPrebidClient(t *testing.T) {
Height: 250,
},
}
- cobj[2] = &CacheObject{
+ cobj[1] = &CacheObject{
IsVideo: false,
Value: &BidCache{
Adm: "",
@@ -111,14 +102,10 @@ func TestPrebidClient(t *testing.T) {
Height: 250,
},
}
- cobj[3] = &CacheObject{
+ cobj[2] = &CacheObject{
IsVideo: true,
Value: "",
}
- cobj[4] = &CacheObject{
- IsVideo: true,
- Value: "\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n
\n
\n\n\n\n\n
\n\n
\n\n\n\n\n\n\n\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
- }
InitPrebidCache(server.URL)
ctx := context.TODO()
@@ -136,12 +123,6 @@ func TestPrebidClient(t *testing.T) {
if cobj[2].UUID != "UUID-3" {
t.Errorf("Third object UUID was '%s', should have been 'UUID-3'", cobj[2].UUID)
}
- if cobj[3].UUID != "UUID-4" {
- t.Errorf("Fourth object UUID was '%s', should have been 'UUID-4'", cobj[3].UUID)
- }
- if cobj[4].UUID != "UUID-5" {
- t.Errorf("Fifth object UUID was '%s', should have been 'UUID-5'", cobj[4].UUID)
- }
delay = 5 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go
index 41f1c39447b..451f2b40238 100644
--- a/privacy/ccpa/consentwriter.go
+++ b/privacy/ccpa/consentwriter.go
@@ -1,8 +1,12 @@
package ccpa
-import "github.com/mxmCherry/openrtb/v15/openrtb2"
+import (
+ "github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
-// ConsentWriter implements the PolicyWriter interface for CCPA.
+// ConsentWriter implements the old PolicyWriter interface for CCPA.
+// This is used where we have not converted to RequestWrapper yet
type ConsentWriter struct {
Consent string
}
@@ -12,12 +16,11 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error {
if req == nil {
return nil
}
-
- regs, err := buildRegs(c.Consent, req.Regs)
- if err != nil {
+ reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req}
+ if regsExt, err := reqWrap.GetRegExt(); err == nil {
+ regsExt.SetUSPrivacy(c.Consent)
+ } else {
return err
}
- req.Regs = regs
-
- return nil
+ return reqWrap.RebuildRequest()
}
diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go
index d59428626b8..28dfd41785e 100644
--- a/privacy/ccpa/consentwriter_test.go
+++ b/privacy/ccpa/consentwriter_test.go
@@ -5,10 +5,60 @@ import (
"testing"
"github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
)
+// RegExt.SetUSPrivacy() is the new ConsentWriter
func TestConsentWriter(t *testing.T) {
+ consent := "anyConsent"
+ testCases := []struct {
+ description string
+ request *openrtb2.BidRequest
+ expected *openrtb2.BidRequest
+ expectedError bool
+ }{
+ {
+ description: "Nil Request",
+ request: nil,
+ expected: nil,
+ },
+ {
+ description: "Success",
+ request: &openrtb2.BidRequest{},
+ expected: &openrtb2.BidRequest{
+ Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)},
+ },
+ },
+ {
+ description: "Error With Regs.Ext - Does Not Mutate",
+ request: &openrtb2.BidRequest{
+ Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)},
+ },
+ expectedError: false,
+ expected: &openrtb2.BidRequest{
+ Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)},
+ },
+ },
+ }
+
+ for _, test := range testCases {
+
+ reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request}
+ var err error
+ regsExt, err1 := reqWrapper.GetRegExt()
+ if err1 == nil {
+ regsExt.SetUSPrivacy(consent)
+ if reqWrapper.BidRequest != nil {
+ err = reqWrapper.RebuildRequest()
+ }
+ }
+ assertError(t, test.expectedError, err, test.description)
+ assert.Equal(t, test.expected, reqWrapper.BidRequest, test.description)
+ }
+}
+
+func TestConsentWriterLegacy(t *testing.T) {
consent := "anyConsent"
testCases := []struct {
description string
diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go
index d57ba8deaa4..39322317df5 100644
--- a/privacy/ccpa/policy.go
+++ b/privacy/ccpa/policy.go
@@ -1,8 +1,6 @@
package ccpa
import (
- "encoding/json"
- "errors"
"fmt"
"github.com/mxmCherry/openrtb/v15/openrtb2"
@@ -15,8 +13,8 @@ type Policy struct {
NoSaleBidders []string
}
-// ReadFromRequest extracts the CCPA regulatory information from an OpenRTB bid request.
-func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) {
+// ReadFromRequestWrapper extracts the CCPA regulatory information from an OpenRTB bid request.
+func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper) (Policy, error) {
var consent string
var noSaleBidders []string
@@ -25,174 +23,80 @@ func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) {
}
// Read consent from request.regs.ext
- if req.Regs != nil && len(req.Regs.Ext) > 0 {
- var ext openrtb_ext.ExtRegs
- if err := json.Unmarshal(req.Regs.Ext, &ext); err != nil {
- return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err)
- }
- consent = ext.USPrivacy
+ regsExt, err := req.GetRegExt()
+ if err != nil {
+ return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err)
+ }
+ if regsExt != nil {
+ consent = regsExt.GetUSPrivacy()
}
-
// Read no sale bidders from request.ext.prebid
- if len(req.Ext) > 0 {
- var ext openrtb_ext.ExtRequest
- if err := json.Unmarshal(req.Ext, &ext); err != nil {
- return Policy{}, fmt.Errorf("error reading request.ext.prebid: %s", err)
- }
- noSaleBidders = ext.Prebid.NoSale
+ reqExt, err := req.GetRequestExt()
+ if err != nil {
+ return Policy{}, fmt.Errorf("error reading request.ext: %s", err)
+ }
+ reqPrebid := reqExt.GetPrebid()
+ if reqPrebid != nil {
+ noSaleBidders = reqPrebid.NoSale
}
return Policy{consent, noSaleBidders}, nil
}
+func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) {
+ return ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: req})
+}
+
// Write mutates an OpenRTB bid request with the CCPA regulatory information.
-func (p Policy) Write(req *openrtb2.BidRequest) error {
+func (p Policy) Write(req *openrtb_ext.RequestWrapper) error {
if req == nil {
return nil
}
- regs, err := buildRegs(p.Consent, req.Regs)
+ regsExt, err := req.GetRegExt()
if err != nil {
return err
}
- ext, err := buildExt(p.NoSaleBidders, req.Ext)
+
+ reqExt, err := req.GetRequestExt()
if err != nil {
return err
}
- req.Regs = regs
- req.Ext = ext
+ regsExt.SetUSPrivacy(p.Consent)
+ setPrebidNoSale(p.NoSaleBidders, reqExt)
return nil
}
-func buildRegs(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) {
- if consent == "" {
- return buildRegsClear(regs)
- }
- return buildRegsWrite(consent, regs)
-}
-
-func buildRegsClear(regs *openrtb2.Regs) (*openrtb2.Regs, error) {
- if regs == nil || len(regs.Ext) == 0 {
- return regs, nil
- }
-
- var extMap map[string]interface{}
- if err := json.Unmarshal(regs.Ext, &extMap); err != nil {
- return nil, err
- }
-
- delete(extMap, "us_privacy")
-
- // Remove entire ext if it's now empty
- if len(extMap) == 0 {
- regsResult := *regs
- regsResult.Ext = nil
- return ®sResult, nil
- }
-
- // Marshal ext if there are still other fields
- var regsResult openrtb2.Regs
- ext, err := json.Marshal(extMap)
- if err == nil {
- regsResult = *regs
- regsResult.Ext = ext
- }
- return ®sResult, err
-}
-
-func buildRegsWrite(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) {
- if regs == nil {
- return marshalRegsExt(openrtb2.Regs{}, openrtb_ext.ExtRegs{USPrivacy: consent})
- }
-
- if regs.Ext == nil {
- return marshalRegsExt(*regs, openrtb_ext.ExtRegs{USPrivacy: consent})
- }
-
- var extMap map[string]interface{}
- if err := json.Unmarshal(regs.Ext, &extMap); err != nil {
- return nil, err
- }
-
- extMap["us_privacy"] = consent
- return marshalRegsExt(*regs, extMap)
-}
-
-func marshalRegsExt(regs openrtb2.Regs, ext interface{}) (*openrtb2.Regs, error) {
- extJSON, err := json.Marshal(ext)
- if err == nil {
- regs.Ext = extJSON
- }
- return ®s, err
-}
-
-func buildExt(noSaleBidders []string, ext json.RawMessage) (json.RawMessage, error) {
+func setPrebidNoSale(noSaleBidders []string, ext *openrtb_ext.RequestExt) {
if len(noSaleBidders) == 0 {
- return buildExtClear(ext)
+ setPrebidNoSaleClear(ext)
+ } else {
+ setPrebidNoSaleWrite(noSaleBidders, ext)
}
- return buildExtWrite(noSaleBidders, ext)
}
-func buildExtClear(ext json.RawMessage) (json.RawMessage, error) {
- if len(ext) == 0 {
- return ext, nil
- }
-
- var extMap map[string]interface{}
- if err := json.Unmarshal(ext, &extMap); err != nil {
- return nil, err
- }
-
- prebidExt, exists := extMap["prebid"]
- if !exists {
- return ext, nil
- }
-
- // Verify prebid is an object
- prebidExtMap, ok := prebidExt.(map[string]interface{})
- if !ok {
- return nil, errors.New("request.ext.prebid is not a json object")
+func setPrebidNoSaleClear(ext *openrtb_ext.RequestExt) {
+ prebid := ext.GetPrebid()
+ if prebid == nil {
+ return
}
// Remove no sale member
- delete(prebidExtMap, "nosale")
- if len(prebidExtMap) == 0 {
- delete(extMap, "prebid")
- }
-
- // Remove entire ext if it's empty
- if len(extMap) == 0 {
- return nil, nil
- }
-
- return json.Marshal(extMap)
+ prebid.NoSale = []string{}
+ ext.SetPrebid(prebid)
}
-func buildExtWrite(noSaleBidders []string, ext json.RawMessage) (json.RawMessage, error) {
- if len(ext) == 0 {
- return json.Marshal(openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{NoSale: noSaleBidders}})
- }
-
- var extMap map[string]interface{}
- if err := json.Unmarshal(ext, &extMap); err != nil {
- return nil, err
+func setPrebidNoSaleWrite(noSaleBidders []string, ext *openrtb_ext.RequestExt) {
+ if ext == nil {
+ // This should hopefully not be possible. The only caller insures that this has been initialized
+ return
}
- var prebidExt map[string]interface{}
- if prebidExtInterface, exists := extMap["prebid"]; exists {
- // Reference Existing Prebid Ext Map
- if prebidExtMap, ok := prebidExtInterface.(map[string]interface{}); ok {
- prebidExt = prebidExtMap
- } else {
- return nil, errors.New("request.ext.prebid is not a json object")
- }
- } else {
- // Create New Empty Prebid Ext Map
- prebidExt = make(map[string]interface{})
- extMap["prebid"] = prebidExt
+ prebid := ext.GetPrebid()
+ if prebid == nil {
+ prebid = &openrtb_ext.ExtRequestPrebid{}
}
-
- prebidExt["nosale"] = noSaleBidders
- return json.Marshal(extMap)
+ prebid.NoSale = noSaleBidders
+ ext.SetPrebid(prebid)
}
diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go
index 416ebffa31a..ca6d0f8acf2 100644
--- a/privacy/ccpa/policy_test.go
+++ b/privacy/ccpa/policy_test.go
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mxmCherry/openrtb/v15/openrtb2"
+ "github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
)
@@ -155,7 +156,8 @@ func TestReadFromRequest(t *testing.T) {
}
for _, test := range testCases {
- result, err := ReadFromRequest(test.request)
+ reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request}
+ result, err := ReadFromRequestWrapper(reqWrapper)
assertError(t, test.expectedError, err, test.description)
assert.Equal(t, test.expectedPolicy, result, test.description)
}
@@ -209,9 +211,20 @@ func TestWrite(t *testing.T) {
}
for _, test := range testCases {
- err := test.policy.Write(test.request)
+ reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request}
+ var err error
+ _, err = reqWrapper.GetRegExt()
+ if err == nil {
+ _, err = reqWrapper.GetRequestExt()
+ if err == nil {
+ err = test.policy.Write(reqWrapper)
+ if err == nil && reqWrapper.BidRequest != nil {
+ err = reqWrapper.RebuildRequest()
+ }
+ }
+ }
assertError(t, test.expectedError, err, test.description)
- assert.Equal(t, test.expected, test.request, test.description)
+ assert.Equal(t, test.expected, reqWrapper.BidRequest, test.description)
}
}
@@ -237,6 +250,9 @@ func TestBuildRegs(t *testing.T) {
regs: &openrtb2.Regs{
Ext: json.RawMessage(`malformed`),
},
+ expected: &openrtb2.Regs{
+ Ext: json.RawMessage(`malformed`),
+ },
expectedError: true,
},
{
@@ -253,14 +269,22 @@ func TestBuildRegs(t *testing.T) {
regs: &openrtb2.Regs{
Ext: json.RawMessage(`malformed`),
},
+ expected: &openrtb2.Regs{
+ Ext: json.RawMessage(`malformed`),
+ },
expectedError: true,
},
}
for _, test := range testCases {
- result, err := buildRegs(test.consent, test.regs)
+ request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: test.regs}}
+ regsExt, err := request.GetRegExt()
+ if err == nil {
+ regsExt.SetUSPrivacy(test.consent)
+ request.RebuildRequest()
+ }
assertError(t, test.expectedError, err, test.description)
- assert.Equal(t, test.expected, result, test.description)
+ assert.Equal(t, test.expected, request.Regs, test.description)
}
}
@@ -274,7 +298,7 @@ func TestBuildRegsClear(t *testing.T) {
{
description: "Nil Regs",
regs: nil,
- expected: nil,
+ expected: &openrtb2.Regs{Ext: nil},
},
{
description: "Nil Regs.Ext",
@@ -297,21 +321,28 @@ func TestBuildRegsClear(t *testing.T) {
expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any"}`)},
},
{
- description: "Invalid Regs.Ext Type - Still Cleared",
- regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)},
- expected: &openrtb2.Regs{},
+ description: "Invalid Regs.Ext Type - Returns Error, doesn't clear",
+ regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)},
+ expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)},
+ expectedError: true,
},
{
description: "Malformed Regs.Ext",
regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)},
+ expected: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)},
expectedError: true,
},
}
for _, test := range testCases {
- result, err := buildRegsClear(test.regs)
+ request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: test.regs}}
+ regsExt, err := request.GetRegExt()
+ if err == nil {
+ regsExt.SetUSPrivacy("")
+ request.RebuildRequest()
+ }
assertError(t, test.expectedError, err, test.description)
- assert.Equal(t, test.expected, result, test.description)
+ assert.Equal(t, test.expected, request.Regs, test.description)
}
}
@@ -354,23 +385,30 @@ func TestBuildRegsWrite(t *testing.T) {
expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any","us_privacy":"anyConsent"}`)},
},
{
- description: "Invalid Regs.Ext Type - Still Overwrites",
- consent: "anyConsent",
- regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)},
- expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)},
+ description: "Invalid Regs.Ext Type - Doesn't Overwrite",
+ consent: "anyConsent",
+ regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)},
+ expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)},
+ expectedError: true,
},
{
description: "Malformed Regs.Ext",
consent: "anyConsent",
regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)},
+ expected: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)},
expectedError: true,
},
}
for _, test := range testCases {
- result, err := buildRegsWrite(test.consent, test.regs)
+ request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: test.regs}}
+ regsExt, err := request.GetRegExt()
+ if err == nil {
+ regsExt.SetUSPrivacy(test.consent)
+ request.RebuildRequest()
+ }
assertError(t, test.expectedError, err, test.description)
- assert.Equal(t, test.expected, result, test.description)
+ assert.Equal(t, test.expected, request.Regs, test.description)
}
}
@@ -415,7 +453,14 @@ func TestBuildExt(t *testing.T) {
}
for _, test := range testCases {
- result, err := buildExt(test.noSaleBidders, test.ext)
+ request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.ext}}
+ reqExt, err := request.GetRequestExt()
+ var result json.RawMessage
+ if err == nil {
+ setPrebidNoSale(test.noSaleBidders, reqExt)
+ err = request.RebuildRequest()
+ result = request.Ext
+ }
assertError(t, test.expectedError, err, test.description)
assert.Equal(t, test.expected, result, test.description)
}
@@ -460,13 +505,13 @@ func TestBuildExtClear(t *testing.T) {
},
{
description: "Leaves Other Ext.Prebid Values",
- ext: json.RawMessage(`{"prebid":{"nosale":["a","b"],"other":"any"}}`),
- expected: json.RawMessage(`{"prebid":{"other":"any"}}`),
+ ext: json.RawMessage(`{"prebid":{"nosale":["a","b"],"aliases":{"a":"b"}}}`),
+ expected: json.RawMessage(`{"prebid":{"aliases":{"a":"b"}}}`),
},
{
description: "Leaves All Other Values",
- ext: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"other":"123"}}`),
- expected: json.RawMessage(`{"other":"ABC","prebid":{"other":"123"}}`),
+ ext: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"supportdeals":true}}`),
+ expected: json.RawMessage(`{"other":"ABC","prebid":{"supportdeals":true}}`),
},
{
description: "Malformed Ext",
@@ -486,7 +531,14 @@ func TestBuildExtClear(t *testing.T) {
}
for _, test := range testCases {
- result, err := buildExtClear(test.ext)
+ request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.ext}}
+ reqExt, err := request.GetRequestExt()
+ var result json.RawMessage
+ if err == nil {
+ setPrebidNoSaleClear(reqExt)
+ err = request.RebuildRequest()
+ result = request.Ext
+ }
assertError(t, test.expectedError, err, test.description)
assert.Equal(t, test.expected, result, test.description)
}
@@ -539,43 +591,56 @@ func TestBuildExtWrite(t *testing.T) {
{
description: "Leaves Other Ext.Prebid Values",
noSaleBidders: []string{"a", "b"},
- ext: json.RawMessage(`{"prebid":{"other":"any"}}`),
- expected: json.RawMessage(`{"prebid":{"nosale":["a","b"],"other":"any"}}`),
+ ext: json.RawMessage(`{"prebid":{"supportdeals":true}}`),
+ expected: json.RawMessage(`{"prebid":{"supportdeals":true,"nosale":["a","b"]}}`),
},
{
description: "Leaves All Other Values",
noSaleBidders: []string{"a", "b"},
- ext: json.RawMessage(`{"other":"ABC","prebid":{"other":"123"}}`),
- expected: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"other":"123"}}`),
+ ext: json.RawMessage(`{"other":"ABC","prebid":{"aliases":{"a":"b"}}}`),
+ expected: json.RawMessage(`{"other":"ABC","prebid":{"aliases":{"a":"b"},"nosale":["a","b"]}}`),
},
{
description: "Invalid Ext.Prebid No Sale Type - Still Overrides",
noSaleBidders: []string{"a", "b"},
ext: json.RawMessage(`{"prebid":{"nosale":123}}`),
- expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`),
+ expected: json.RawMessage(`{"prebid":{"nosale":123}}`),
+ expectedError: true,
},
{
description: "Invalid Ext.Prebid Type ",
noSaleBidders: []string{"a", "b"},
ext: json.RawMessage(`{"prebid":"wrongtype"}`),
+ expected: json.RawMessage(`{"prebid":"wrongtype"}`),
expectedError: true,
},
{
description: "Malformed Ext",
noSaleBidders: []string{"a", "b"},
ext: json.RawMessage(`{malformed`),
+ expected: json.RawMessage(`{malformed`),
expectedError: true,
},
{
description: "Malformed Ext.Prebid",
noSaleBidders: []string{"a", "b"},
ext: json.RawMessage(`{"prebid":malformed}`),
+ expected: json.RawMessage(`{"prebid":malformed}`),
expectedError: true,
},
}
for _, test := range testCases {
- result, err := buildExtWrite(test.noSaleBidders, test.ext)
+ request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.ext}}
+ reqExt, err := request.GetRequestExt()
+ var result json.RawMessage
+ if err == nil {
+ setPrebidNoSaleWrite(test.noSaleBidders, reqExt)
+ err = request.RebuildRequest()
+ result = request.Ext
+ } else {
+ result = test.ext
+ }
assertError(t, test.expectedError, err, test.description)
assert.Equal(t, test.expected, result, test.description)
}
diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go
index dc8f56425c5..9274c5b58be 100644
--- a/privacy/gdpr/policy_test.go
+++ b/privacy/gdpr/policy_test.go
@@ -17,11 +17,6 @@ func TestValidateConsent(t *testing.T) {
consent: "",
expected: false,
},
- {
- description: "TCF1 Valid",
- consent: "BONV8oqONXwgmADACHENAO7pqzAAppY",
- expected: true,
- },
{
description: "TCF2 Valid",
consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
@@ -33,4 +28,4 @@ func TestValidateConsent(t *testing.T) {
result := ValidateConsent(test.consent)
assert.Equal(t, test.expected, result, test.description)
}
-}
+}
\ No newline at end of file
diff --git a/privacy/scrubber.go b/privacy/scrubber.go
index edaa5bb07c6..e07ebd0581b 100644
--- a/privacy/scrubber.go
+++ b/privacy/scrubber.go
@@ -225,11 +225,8 @@ func scrubUserExtIDs(userExt json.RawMessage) json.RawMessage {
}
_, hasEids := userExtParsed["eids"]
- _, hasDigitrust := userExtParsed["digitrust"]
- if hasEids || hasDigitrust {
+ if hasEids {
delete(userExtParsed, "eids")
- delete(userExtParsed, "digitrust")
-
result, err := json.Marshal(userExtParsed)
if err == nil {
return result
diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go
index 9207315f593..1198d0bbc9c 100644
--- a/privacy/scrubber_test.go
+++ b/privacy/scrubber_test.go
@@ -202,7 +202,7 @@ func TestScrubUser(t *testing.T) {
BuyerUID: "anyBuyerUID",
Yob: 42,
Gender: "anyGender",
- Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+ Ext: json.RawMessage(`{}`),
Geo: &openrtb2.Geo{
Lat: 123.456,
Lon: 678.89,
@@ -327,7 +327,7 @@ func TestScrubUser(t *testing.T) {
BuyerUID: "anyBuyerUID",
Yob: 42,
Gender: "anyGender",
- Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+ Ext: json.RawMessage(`{}`),
Geo: &openrtb2.Geo{},
},
scrubUser: ScrubStrategyUserNone,
@@ -340,7 +340,7 @@ func TestScrubUser(t *testing.T) {
BuyerUID: "anyBuyerUID",
Yob: 42,
Gender: "anyGender",
- Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+ Ext: json.RawMessage(`{}`),
Geo: &openrtb2.Geo{
Lat: 123.46,
Lon: 678.89,
@@ -359,7 +359,7 @@ func TestScrubUser(t *testing.T) {
BuyerUID: "anyBuyerUID",
Yob: 42,
Gender: "anyGender",
- Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+ Ext: json.RawMessage(`{}`),
Geo: &openrtb2.Geo{
Lat: 123.456,
Lon: 678.89,
@@ -586,18 +586,18 @@ func TestScrubUserExtIDs(t *testing.T) {
expected: json.RawMessage(`{"anyExisting":42}}`),
},
{
- description: "Remove eids + digitrust",
- userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+ description: "Remove eids",
+ userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`),
expected: json.RawMessage(`{}`),
},
{
- description: "Remove eids + digitrust - With Other Data",
- userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+ description: "Remove eids - With Other Data",
+ userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`),
expected: json.RawMessage(`{"anyExisting":42}`),
},
{
- description: "Remove eids + digitrust - With Other Nested Data",
- userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+ description: "Remove eids - With Other Nested Data",
+ userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`),
expected: json.RawMessage(`{"anyExisting":{"existing":42}}`),
},
{
@@ -620,21 +620,6 @@ func TestScrubUserExtIDs(t *testing.T) {
userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`),
expected: json.RawMessage(`{"anyExisting":{"existing":42}}`),
},
- {
- description: "Remove digitrust Only",
- userExt: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
- expected: json.RawMessage(`{}`),
- },
- {
- description: "Remove digitrust Only - With Other Data",
- userExt: json.RawMessage(`{"anyExisting":42,"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
- expected: json.RawMessage(`{"anyExisting":42}`),
- },
- {
- description: "Remove digitrust Only - With Other Nested Data",
- userExt: json.RawMessage(`{"anyExisting":{"existing":42},"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
- expected: json.RawMessage(`{"anyExisting":{"existing":42}}`),
- },
}
for _, test := range testCases {
diff --git a/router/router.go b/router/router.go
index 5b8ec014a1c..58bdee057f6 100644
--- a/router/router.go
+++ b/router/router.go
@@ -7,6 +7,10 @@ import (
"database/sql"
"encoding/json"
"fmt"
+ "github.com/prebid/prebid-server/analytics"
+ "github.com/prebid/prebid-server/stored_requests"
+ "github.com/prebid/prebid-server/usersync"
+ "github.com/prometheus/client_golang/prometheus"
"io/ioutil"
"net"
"net/http"
@@ -14,11 +18,6 @@ import (
"strings"
"time"
- "github.com/prebid/prebid-server/analytics"
- "github.com/prebid/prebid-server/stored_requests"
- "github.com/prebid/prebid-server/usersync"
- "github.com/prometheus/client_golang/prometheus"
-
"github.com/prebid/prebid-server/currency"
"github.com/prebid/prebid-server/errortypes"
@@ -29,7 +28,6 @@ import (
"github.com/prebid/prebid-server/adapters/appnexus"
"github.com/prebid/prebid-server/adapters/conversant"
"github.com/prebid/prebid-server/adapters/ix"
- "github.com/prebid/prebid-server/adapters/lifestreet"
"github.com/prebid/prebid-server/adapters/pubmatic"
"github.com/prebid/prebid-server/adapters/pulsepoint"
"github.com/prebid/prebid-server/adapters/rubicon"
@@ -181,7 +179,6 @@ func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter {
"pulsepoint": pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPulsepoint)].Endpoint),
"rubicon": rubicon.NewRubiconLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint,
cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Username, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Password, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker),
- "lifestreet": lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderLifestreet)].Endpoint),
"conversant": conversant.NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderConversant)].Endpoint),
"adform": adform.NewAdformLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint),
"sovrn": sovrn.NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint),
diff --git a/static/bidder-info/adagio.yaml b/static/bidder-info/adagio.yaml
new file mode 100644
index 00000000000..3661191b3a1
--- /dev/null
+++ b/static/bidder-info/adagio.yaml
@@ -0,0 +1,12 @@
+maintainer:
+ email: "dev@adagio.io"
+gvlVendorID: 617
+modifyingVastXmlAllowed: false
+debug:
+ allow: true
+capabilities:
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
\ No newline at end of file
diff --git a/static/bidder-info/adf.yaml b/static/bidder-info/adf.yaml
new file mode 100644
index 00000000000..776e208f562
--- /dev/null
+++ b/static/bidder-info/adf.yaml
@@ -0,0 +1,14 @@
+maintainer:
+ email: "scope.sspp@adform.com"
+gvlVendorID: 50
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - native
+ - video
+ site:
+ mediaTypes:
+ - banner
+ - native
+ - video
diff --git a/static/bidder-info/adhese.yaml b/static/bidder-info/adhese.yaml
index 742d78344ce..25058e82409 100644
--- a/static/bidder-info/adhese.yaml
+++ b/static/bidder-info/adhese.yaml
@@ -1,5 +1,7 @@
maintainer:
email: info@adhese.com
+gvlVendorID: 553
+modifyingVastXmlAllowed: true
capabilities:
app:
mediaTypes:
@@ -8,4 +10,4 @@ capabilities:
site:
mediaTypes:
- banner
- - video
\ No newline at end of file
+ - video
diff --git a/static/bidder-info/adocean.yaml b/static/bidder-info/adocean.yaml
index b3a23718a5a..6374d752c34 100644
--- a/static/bidder-info/adocean.yaml
+++ b/static/bidder-info/adocean.yaml
@@ -2,6 +2,9 @@ maintainer:
email: "aoteam@gemius.com"
gvlVendorID: 328
capabilities:
+ app:
+ mediaTypes:
+ - banner
site:
mediaTypes:
- banner
diff --git a/static/bidder-info/adtarget.yaml b/static/bidder-info/adtarget.yaml
index d52f18ac697..cc064b9ca6b 100644
--- a/static/bidder-info/adtarget.yaml
+++ b/static/bidder-info/adtarget.yaml
@@ -1,5 +1,6 @@
maintainer:
email: "kamil@adtarget.com.tr"
+gvlVendorID: 779
capabilities:
app:
mediaTypes:
diff --git a/static/bidder-info/algorix.yaml b/static/bidder-info/algorix.yaml
new file mode 100644
index 00000000000..b8301d6cb79
--- /dev/null
+++ b/static/bidder-info/algorix.yaml
@@ -0,0 +1,8 @@
+maintainer:
+ email: "xunyunbo@algorix.co"
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
\ No newline at end of file
diff --git a/static/bidder-info/axonix.yaml b/static/bidder-info/axonix.yaml
new file mode 100644
index 00000000000..3c73501d9cc
--- /dev/null
+++ b/static/bidder-info/axonix.yaml
@@ -0,0 +1,15 @@
+maintainer:
+ email: support.axonix@emodoinc.com
+gvlVendorID: 678
+modifyingVastXmlAllowed: true
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
diff --git a/static/bidder-info/bidmyadz.yaml b/static/bidder-info/bidmyadz.yaml
new file mode 100644
index 00000000000..70a995a2798
--- /dev/null
+++ b/static/bidder-info/bidmyadz.yaml
@@ -0,0 +1,13 @@
+maintainer:
+ email: "contact@bidmyadz.com"
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
\ No newline at end of file
diff --git a/static/bidder-info/bidscube.yaml b/static/bidder-info/bidscube.yaml
new file mode 100644
index 00000000000..046eb6224d7
--- /dev/null
+++ b/static/bidder-info/bidscube.yaml
@@ -0,0 +1,14 @@
+maintainer:
+ email: "support@bidscube.com"
+modifyingVastXmlAllowed: true
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
diff --git a/static/bidder-info/bmtm.yaml b/static/bidder-info/bmtm.yaml
new file mode 100644
index 00000000000..c13bf17db73
--- /dev/null
+++ b/static/bidder-info/bmtm.yaml
@@ -0,0 +1,8 @@
+maintainer:
+ email: dev@brightmountainmedia.com
+modifyingVastXmlAllowed: false
+capabilities:
+ site:
+ mediaTypes:
+ - banner
+ - video
diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml
index b27e1fae369..bfa098ba39d 100644
--- a/static/bidder-info/criteo.yaml
+++ b/static/bidder-info/criteo.yaml
@@ -1,5 +1,5 @@
maintainer:
- email: "pi-direct@criteo.com"
+ email: "prebid@criteo.com"
gvlVendorID: 91
capabilities:
app:
diff --git a/static/bidder-info/e_volution.yaml b/static/bidder-info/e_volution.yaml
new file mode 100644
index 00000000000..6ea9dc7bac2
--- /dev/null
+++ b/static/bidder-info/e_volution.yaml
@@ -0,0 +1,14 @@
+maintainer:
+ email: "admin@e-volution.ai"
+gvlVendorID: 957
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
\ No newline at end of file
diff --git a/static/bidder-info/epom.yaml b/static/bidder-info/epom.yaml
index 32afa346c9e..991944ea35f 100644
--- a/static/bidder-info/epom.yaml
+++ b/static/bidder-info/epom.yaml
@@ -1,6 +1,7 @@
maintainer:
email: "support@epom.com"
modifyingVastXmlAllowed: true
+gvlVendorID: 849
capabilities:
app:
mediaTypes:
diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml
index 3f8cdd8cb91..d62a2c9239d 100644
--- a/static/bidder-info/inmobi.yaml
+++ b/static/bidder-info/inmobi.yaml
@@ -1,8 +1,13 @@
maintainer:
- email: "prebid-support@inmobi.com"
-
+ email: "technology-irv@inmobi.com"
+gvlVendorID: 333
capabilities:
app:
mediaTypes:
- banner
- video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
diff --git a/static/bidder-info/interactiveoffers.yaml b/static/bidder-info/interactiveoffers.yaml
new file mode 100644
index 00000000000..9a36076bab9
--- /dev/null
+++ b/static/bidder-info/interactiveoffers.yaml
@@ -0,0 +1,9 @@
+maintainer:
+ email: "support@interactiveoffers.com"
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ site:
+ mediaTypes:
+ - banner
diff --git a/static/bidder-info/kayzen.yaml b/static/bidder-info/kayzen.yaml
new file mode 100644
index 00000000000..45fd43c5a11
--- /dev/null
+++ b/static/bidder-info/kayzen.yaml
@@ -0,0 +1,15 @@
+maintainer:
+ email: "platform-dev@kayzen.io"
+gvlVendorID: 528
+modifyingVastXmlAllowed: true
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
diff --git a/static/bidder-info/lifestreet.yaml b/static/bidder-info/madvertise.yaml
similarity index 68%
rename from static/bidder-info/lifestreet.yaml
rename to static/bidder-info/madvertise.yaml
index 34dc4eca2d9..6156940e6d5 100644
--- a/static/bidder-info/lifestreet.yaml
+++ b/static/bidder-info/madvertise.yaml
@@ -1,11 +1,11 @@
maintainer:
- email: "mobile.tech@lifestreet.com"
-gvlVendorID: 67
+ email: "support@madvertise.com"
+gvlVendorID: 153
capabilities:
app:
mediaTypes:
- banner
+ - video
site:
mediaTypes:
- banner
- - video
diff --git a/static/bidder-info/marsmedia.yaml b/static/bidder-info/marsmedia.yaml
index e0267205a27..143b893ed9b 100644
--- a/static/bidder-info/marsmedia.yaml
+++ b/static/bidder-info/marsmedia.yaml
@@ -1,5 +1,6 @@
maintainer:
email: "prebid@mars.media"
+gvlVendorID: 776
capabilities:
app:
mediaTypes:
diff --git a/static/bidder-info/mobilefuse.yaml b/static/bidder-info/mobilefuse.yaml
index 178e407d927..18a90a8866e 100644
--- a/static/bidder-info/mobilefuse.yaml
+++ b/static/bidder-info/mobilefuse.yaml
@@ -1,5 +1,6 @@
maintainer:
email: prebid@mobilefuse.com
+gvlVendorID: 909
capabilities:
app:
mediaTypes:
diff --git a/static/bidder-info/operaads.yaml b/static/bidder-info/operaads.yaml
new file mode 100644
index 00000000000..b95d81155c1
--- /dev/null
+++ b/static/bidder-info/operaads.yaml
@@ -0,0 +1,14 @@
+maintainer:
+ email: adtech-prebid-group@opera.com
+modifyingVastXmlAllowed: true
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
\ No newline at end of file
diff --git a/static/bidder-info/pubnative.yaml b/static/bidder-info/pubnative.yaml
index 65bc2659dfe..44bba23d7f3 100644
--- a/static/bidder-info/pubnative.yaml
+++ b/static/bidder-info/pubnative.yaml
@@ -1,5 +1,6 @@
maintainer:
email: product@pubnative.net
+gvlVendorID: 512
capabilities:
app:
mediaTypes:
diff --git a/static/bidder-info/revcontent.yaml b/static/bidder-info/revcontent.yaml
index c0216dfca61..57e887565ce 100644
--- a/static/bidder-info/revcontent.yaml
+++ b/static/bidder-info/revcontent.yaml
@@ -1,5 +1,6 @@
maintainer:
email: "developers@revcontent.com"
+gvlVendorID: 203
capabilities:
app:
mediaTypes:
@@ -8,4 +9,4 @@ capabilities:
site:
mediaTypes:
- banner
- - native
\ No newline at end of file
+ - native
diff --git a/static/bidder-info/sa_lunamedia.yaml b/static/bidder-info/sa_lunamedia.yaml
new file mode 100644
index 00000000000..181e1fd6c73
--- /dev/null
+++ b/static/bidder-info/sa_lunamedia.yaml
@@ -0,0 +1,14 @@
+maintainer:
+ email: "support@lunamedia.io"
+gvlVendorID: 998
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
\ No newline at end of file
diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml
index db3e61e5cc6..b73edb23d18 100644
--- a/static/bidder-info/smaato.yaml
+++ b/static/bidder-info/smaato.yaml
@@ -1,5 +1,6 @@
maintainer:
email: "prebid@smaato.com"
+gvlVendorID: 82
capabilities:
app:
mediaTypes:
diff --git a/static/bidder-info/smilewanted.yaml b/static/bidder-info/smilewanted.yaml
new file mode 100644
index 00000000000..81b0585bb5e
--- /dev/null
+++ b/static/bidder-info/smilewanted.yaml
@@ -0,0 +1,12 @@
+maintainer:
+ email: "tech@smilewanted.com"
+gvlVendorID: 639
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ site:
+ mediaTypes:
+ - banner
+ - video
diff --git a/static/bidder-info/viewdeos.yaml b/static/bidder-info/viewdeos.yaml
new file mode 100644
index 00000000000..9483e281de0
--- /dev/null
+++ b/static/bidder-info/viewdeos.yaml
@@ -0,0 +1,12 @@
+maintainer:
+ email: "contact@viewdeos.com"
+gvlVendorID: 924
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ site:
+ mediaTypes:
+ - banner
+ - video
diff --git a/static/bidder-params/adagio.json b/static/bidder-params/adagio.json
new file mode 100644
index 00000000000..955c58c73ec
--- /dev/null
+++ b/static/bidder-params/adagio.json
@@ -0,0 +1,94 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Adagio Adapter Params",
+ "description": "A schema which validates params accepted by the Adagio adapter",
+ "type": "object",
+ "required": [
+ "organizationId",
+ "site",
+ "placement"
+ ],
+ "properties": {
+ "organizationId": {
+ "type": "string",
+ "description": "Name to identify the organization",
+ "minLength": 1
+ },
+ "site": {
+ "type": "string",
+ "description": "Name to identify the site",
+ "minLength": 1
+ },
+ "placement": {
+ "type": "string",
+ "description": "Name to identify the placement",
+ "minLength": 1
+ },
+ "pageviewId": {
+ "type": "string",
+ "description": "Name to identify the pageview"
+ },
+ "pagetype": {
+ "type": "string",
+ "description": "Name to identify the page type"
+ },
+ "category": {
+ "type": "string",
+ "description": "Name to identify the category"
+ },
+ "subcategory": {
+ "type": "string",
+ "description": "Name to identify the subcategory"
+ },
+ "environment": {
+ "type": "string",
+ "description": "Name to identify the environment"
+ },
+ "features": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-zA-Z_]": { "type": "string" }
+ }
+ },
+ "prebidVersion:": {
+ "type": "string",
+ "description": "Name to identify the version of Prebid.js"
+ },
+ "debug": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ },
+ "cpm": {
+ "type": "number"
+ },
+ "lazyLoad": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ },
+ "threshold": {
+ "type": "number"
+ },
+ "rootMargin": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "native": {
+ "type": "object",
+ "properties": {
+ "context": {
+ "type": "number"
+ },
+ "plcmttype": {
+ "type": "number"
+ }
+ }
+ }
+ }
+}
diff --git a/static/bidder-params/adf.json b/static/bidder-params/adf.json
new file mode 100644
index 00000000000..a16df36d681
--- /dev/null
+++ b/static/bidder-params/adf.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Adf Adapter Params",
+ "description": "A schema which validates params accepted by the adf adapter",
+ "type": "object",
+ "properties": {
+ "mid": {
+ "type": ["integer", "string"],
+ "pattern": "^\\d+$",
+ "description": "An ID which identifies the placement selling the impression"
+ }
+ },
+ "required": ["mid"]
+}
diff --git a/static/bidder-params/admixer.json b/static/bidder-params/admixer.json
index 886e33ff2bb..78671931561 100644
--- a/static/bidder-params/admixer.json
+++ b/static/bidder-params/admixer.json
@@ -8,7 +8,7 @@
"zone": {
"type": "string",
"description": "Zone ID.",
- "pattern": "^([a-fA-F\\d\\-]{36})$"
+ "pattern": "^([a-fA-F\\d\\-]{32,36})$"
},
"customFloor": {
"type": "number",
@@ -22,4 +22,4 @@
},
"required": ["zone"]
-}
+}
\ No newline at end of file
diff --git a/static/bidder-params/algorix.json b/static/bidder-params/algorix.json
new file mode 100644
index 00000000000..732625f4549
--- /dev/null
+++ b/static/bidder-params/algorix.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "AlgoriX Adapter Params",
+ "description": "A schema which validates params accepted by the AlgoriX adapter",
+ "type": "object",
+ "properties": {
+ "sid": {
+ "type": "string",
+ "description": "Your Sid",
+ "minLength": 1
+ },
+ "token": {
+ "type": "string",
+ "description": "Your Token",
+ "minLength": 1
+ }
+ },
+ "required": ["sid", "token"]
+}
\ No newline at end of file
diff --git a/static/bidder-params/appnexus.json b/static/bidder-params/appnexus.json
index 7da41a67055..ce5ddad0437 100644
--- a/static/bidder-params/appnexus.json
+++ b/static/bidder-params/appnexus.json
@@ -67,6 +67,10 @@
"type": "boolean",
"description": "Boolean to signal AppNexus to apply the relevant payment rule"
},
+ "generate_ad_pod_id": {
+ "type": "boolean",
+ "description": "Boolean to signal AppNexus to add ad pod id to each request"
+ },
"private_sizes" :{
"type": "array",
"items": {
diff --git a/static/bidder-params/lifestreet.json b/static/bidder-params/axonix.json
similarity index 53%
rename from static/bidder-params/lifestreet.json
rename to static/bidder-params/axonix.json
index 2190d761e69..7a3762ce5e2 100644
--- a/static/bidder-params/lifestreet.json
+++ b/static/bidder-params/axonix.json
@@ -1,13 +1,14 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
- "title": "Lifestreet Adapter Params",
- "description": "A schema which validates params accepted by the Lifestreet adapter",
+ "title": "Axonix Adapter Params",
+ "description": "A schema which validates params accepted by the Axonix adapter",
"type": "object",
"properties": {
- "slot_tag": {
+ "supplyId": {
"type": "string",
- "description": "A tag which identifies the ad slot"
+ "minLength": 1,
+ "description": "Unique supply identifier"
}
},
- "required": ["slot_tag"]
+ "required": ["supplyId"]
}
diff --git a/static/bidder-params/beachfront.json b/static/bidder-params/beachfront.json
index a7751b279cc..5b1e71a9ba8 100644
--- a/static/bidder-params/beachfront.json
+++ b/static/bidder-params/beachfront.json
@@ -4,7 +4,7 @@
"description": "A schema which validates params accepted by the Beachfront adapter",
"type": "object",
"properties": {
- "appId" : {
+ "appId": {
"type": "string",
"description": "The id of an inventory target. This can only be used in requests that contain one media type. It will be applied to all imps in the request."
},
@@ -14,17 +14,23 @@
"properties": {
"video" : {
"type": "string",
+ "title": "Video appId",
"description": "An appId string that will be applied to video requests in this imp."
},
"banner" : {
"type": "string",
+ "title": "Banner appId",
"description": "An appId string that will be applied to banner requests in this imp."
}
- }
+ },
+ "anyOf":[
+ {"required":["video"]},
+ {"required":["banner"]}
+ ]
},
"bidfloor": {
"type": "number",
- "description": "The price floor for the bid."
+ "description": "The price floor for the bid. Will override the bidfloor set for the impression."
},
"videoResponseType": {
"type": "string",
@@ -32,9 +38,8 @@
}
},
- "required": ["bidfloor"],
- "oneOf": [{
- "required": ["appId"] }, {
- "required": ["appIds"]
- }]
+ "oneOf": [
+ { "required": ["appIds"] },
+ { "required": ["appId"] }
+ ]
}
diff --git a/static/bidder-params/between.json b/static/bidder-params/between.json
index 032d38fec4b..64863462697 100644
--- a/static/bidder-params/between.json
+++ b/static/bidder-params/between.json
@@ -12,15 +12,6 @@
"publisher_id": {
"type": "string",
"description": "Publisher ID from Between Exchange control panel"
- },
- "bid_floor": {
- "type": "number",
- "description": "The minimum price acceptable for a bid"
- },
- "bid_floor_cur": {
- "type": "string",
- "description": "Currency of bid floor",
- "enum": ["USD", "EUR", "RUB"]
}
},
"required": ["host", "publisher_id"]
diff --git a/static/bidder-params/bidmyadz.json b/static/bidder-params/bidmyadz.json
new file mode 100644
index 00000000000..4e7b1119e08
--- /dev/null
+++ b/static/bidder-params/bidmyadz.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "BidMyAdz Adapter Params",
+ "description": "A schema which validates params accepted by the BidMyAdz adapter",
+ "type": "object",
+ "properties": {
+ "placementId": {
+ "type": "string"
+ }
+ },
+ "required": ["placementId"]
+ }
\ No newline at end of file
diff --git a/static/bidder-params/bidscube.json b/static/bidder-params/bidscube.json
new file mode 100644
index 00000000000..88dc91ab391
--- /dev/null
+++ b/static/bidder-params/bidscube.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "BidsCube Adapter Params",
+ "description": "A schema which validates params accepted by the BidsCube adapter",
+ "type": "object",
+ "properties": {
+ "placementId": {
+ "type": "string",
+ "minLength": 1,
+ "description": "An ID which identifies the BidsCube placement"
+ },
+ "customParams": {
+ "type": "object",
+ "description": "User-defined targeting key-value pairs."
+ }
+ },
+ "required" : [ "placementId" ]
+}
diff --git a/static/bidder-params/bmtm.json b/static/bidder-params/bmtm.json
new file mode 100644
index 00000000000..f1fbbddc7c6
--- /dev/null
+++ b/static/bidder-params/bmtm.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Bright Mountain Media Adapter Params",
+ "description": "A schema which validates params accepted by the Bright Mountain Media adapter",
+ "type": "object",
+ "properties": {
+ "placement_id": {
+ "type": "number",
+ "minimum": 1,
+ "description": "Placement ID from Bright Mountain Media"
+ }
+ },
+ "required": [
+ "placement_id"
+ ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/criteo.json b/static/bidder-params/criteo.json
index 9d348a7eded..88c6fba5d3a 100644
--- a/static/bidder-params/criteo.json
+++ b/static/bidder-params/criteo.json
@@ -1,30 +1,50 @@
-{
- "$schema": "http://json-schema.org/draft-04/schema#",
- "title": "Criteo adapter params",
- "description": "The schema to validate Criteo specific params accepted by Criteo adapter",
- "type": "object",
- "properties": {
- "zoneid": {
- "type": "number",
- "description": "Impression's zone ID.",
- "minimum": 0
- },
- "networkid": {
- "type": "number",
- "description": "Impression's network ID.",
- "minimum": 0
- }
- },
- "anyOf": [
- {
- "required": [
- "zoneid"
- ]
- },
- {
- "required": [
- "networkid"
- ]
- }
- ]
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Criteo adapter params",
+ "description": "The schema to validate Criteo specific params accepted by Criteo adapter",
+ "type": "object",
+ "properties": {
+ "zoneid": {
+ "type": "integer",
+ "description": "Impression's zone ID.",
+ "minimum": 0
+ },
+ "zoneId": {
+ "type": "integer",
+ "description": "Impression's zone ID, preferred.",
+ "minimum": 0
+ },
+ "networkid": {
+ "type": "integer",
+ "description": "Impression's network ID.",
+ "minimum": 0
+ },
+ "networkId": {
+ "type": "integer",
+ "description": "Impression's network ID, preferred.",
+ "minimum": 0
+ }
+ },
+ "anyOf": [
+ {
+ "required": [
+ "zoneid"
+ ]
+ },
+ {
+ "required": [
+ "zoneId"
+ ]
+ },
+ {
+ "required": [
+ "networkid"
+ ]
+ },
+ {
+ "required": [
+ "networkId"
+ ]
+ }
+ ]
}
\ No newline at end of file
diff --git a/static/bidder-params/e_volution.json b/static/bidder-params/e_volution.json
new file mode 100644
index 00000000000..18de2a6062d
--- /dev/null
+++ b/static/bidder-params/e_volution.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "E-volution Adapter Params",
+ "description": "A schema which validates params accepted by the E-volution adapter",
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string",
+ "description": "network or placement key"
+ }
+ },
+ "required": ["key"]
+ }
\ No newline at end of file
diff --git a/static/bidder-params/interactiveoffers.json b/static/bidder-params/interactiveoffers.json
new file mode 100644
index 00000000000..79338dcc40a
--- /dev/null
+++ b/static/bidder-params/interactiveoffers.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Interactive Offers Adapter Params",
+ "description": "A schema which validates params accepted by Interactive Offers adapter",
+ "type": "object",
+ "properties": {
+ "pubid": {
+ "type": "integer",
+ "description": "The publisher id"
+ }
+ },
+ "required": ["pubid"]
+}
\ No newline at end of file
diff --git a/static/bidder-params/ix.json b/static/bidder-params/ix.json
index 155cfa21892..a7a5cb7308a 100644
--- a/static/bidder-params/ix.json
+++ b/static/bidder-params/ix.json
@@ -4,10 +4,20 @@
"description": "A schema which validates params accepted by the Ix adapter",
"type": "object",
"properties": {
+ "siteid": {
+ "type": "string",
+ "minLength": 1,
+ "description": "An ID which identifies the site selling the impression, preferred."
+ },
"siteId": {
"type": "string",
"minLength": 1,
- "description": "An ID which identifies the site selling the impression"
+ "description": "An ID which identifies the site selling the impression."
+ },
+ "siteID": {
+ "type": "string",
+ "minLength": 1,
+ "description": "An ID which identifies the site selling the impression."
},
"size": {
"type": "array",
@@ -19,5 +29,9 @@
"description": "An array of two integer containing the dimension"
}
},
- "required": ["siteId"]
+ "oneOf": [
+ {"required": ["siteid"]},
+ {"required": ["siteId"]},
+ {"required": ["siteID"]}
+ ]
}
diff --git a/static/bidder-params/kayzen.json b/static/bidder-params/kayzen.json
new file mode 100644
index 00000000000..f2256c6b029
--- /dev/null
+++ b/static/bidder-params/kayzen.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Kayzen Adapter Params",
+ "description": "A schema which validates params accepted by the Kayzen adapter",
+ "type": "object",
+
+ "properties": {
+ "zone": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Zone ID"
+ },
+ "exchange": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Exchange/Publisher Name"
+ }
+ },
+ "required": ["zone", "exchange"]
+}
+
diff --git a/static/bidder-params/madvertise.json b/static/bidder-params/madvertise.json
new file mode 100644
index 00000000000..c2fbd941afd
--- /dev/null
+++ b/static/bidder-params/madvertise.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Madvertise Adapter Params",
+ "description": "A schema which validates params accepted by the Madvertise adapter",
+ "type": "object",
+ "properties": {
+ "zoneId": {
+ "type": "string",
+ "minLength": 7,
+ "description": "The zone ID provided by Madvertise"
+ }
+ },
+ "required": [
+ "zoneId"
+ ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/operaads.json b/static/bidder-params/operaads.json
new file mode 100644
index 00000000000..5095c5b2d2b
--- /dev/null
+++ b/static/bidder-params/operaads.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "A schema which validates params accepted by the OperaAds adapter",
+ "properties": {
+ "placementId": {
+ "description": "Placement ID",
+ "type": "string",
+ "minLength": 1
+ },
+ "endpointId": {
+ "description": "Endpoint ID",
+ "type": "string",
+ "minLength": 1
+ },
+ "publisherId": {
+ "description": "Publisher ID",
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": [
+ "placementId",
+ "endpointId",
+ "publisherId"
+ ],
+ "title": "OperaAds Adapter Params",
+ "type": "object"
+}
\ No newline at end of file
diff --git a/static/bidder-params/pangle.json b/static/bidder-params/pangle.json
index 74085cb5e65..b36922c31b7 100644
--- a/static/bidder-params/pangle.json
+++ b/static/bidder-params/pangle.json
@@ -8,9 +8,27 @@
"type": "string",
"description": "Access Token",
"pattern": ".+"
+ },
+ "appid": {
+ "type": "string",
+ "description": "App ID",
+ "pattern": "[0-9]+"
+ },
+ "placementid": {
+ "type": "string",
+ "description": "Placement ID",
+ "pattern": "[0-9]+"
}
},
"required": [
"token"
- ]
-}
+ ],
+ "dependencies": {
+ "placementid": [
+ "appid"
+ ],
+ "appid": [
+ "placementid"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/static/bidder-params/sa_lunamedia.json b/static/bidder-params/sa_lunamedia.json
new file mode 100644
index 00000000000..51ca09098e2
--- /dev/null
+++ b/static/bidder-params/sa_lunamedia.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Sa_Lunamedia Adapter Params",
+ "description": "A schema which validates params accepted by the Sa_Lunamedia adapter",
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string",
+ "description": "network or placement key"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["network", "publisher"]
+ }
+ },
+ "required": ["key"]
+ }
\ No newline at end of file
diff --git a/static/bidder-params/sharethrough.json b/static/bidder-params/sharethrough.json
index ba6580e2a7b..1fe2949bc8f 100644
--- a/static/bidder-params/sharethrough.json
+++ b/static/bidder-params/sharethrough.json
@@ -26,6 +26,16 @@
"bidfloor": {
"type": "number",
"description": "The floor price, or minimum amount, a publisher will accept for an impression, given in CPM in USD"
+ },
+ "data": {
+ "type": "object",
+ "description": "Ad-specific first party data",
+ "properties": {
+ "pbadslot": {
+ "type": "string",
+ "description": "Prebid Ad Slot, see: https://docs.prebid.org/features/pbAdSlot.html"
+ }
+ }
}
},
"required": ["pkey"]
diff --git a/static/bidder-params/smaato.json b/static/bidder-params/smaato.json
index aa91c4bacc5..e4584b86860 100644
--- a/static/bidder-params/smaato.json
+++ b/static/bidder-params/smaato.json
@@ -11,7 +11,25 @@
"adspaceId": {
"type": "string",
"description": "Identifier for specific ad placement is SOMA `adspaceId`"
+ },
+ "adbreakId": {
+ "type": "string",
+ "description": "Identifier for specific adpod placement is SOMA `adbreakId`"
}
},
- "required": ["publisherId","adspaceId"]
+ "required": [
+ "publisherId"
+ ],
+ "anyOf": [
+ {
+ "required": [
+ "adspaceId"
+ ]
+ },
+ {
+ "required": [
+ "adbreakId"
+ ]
+ }
+ ]
}
\ No newline at end of file
diff --git a/static/bidder-params/smilewanted.json b/static/bidder-params/smilewanted.json
new file mode 100644
index 00000000000..be4f9bc142d
--- /dev/null
+++ b/static/bidder-params/smilewanted.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "SmileWanted Adapter Params",
+ "description": "A schema which validates params accepted by the SmileWanted adapter",
+ "type": "object",
+ "properties": {
+ "zoneId": {
+ "type": "string",
+ "description": "An ID which identifies the SmileWanted zone code",
+ "minLength": 1
+ }
+ },
+ "required": ["zoneId"]
+}
diff --git a/static/bidder-params/tappx.json b/static/bidder-params/tappx.json
index f8feb1913e9..1cf101a44f5 100644
--- a/static/bidder-params/tappx.json
+++ b/static/bidder-params/tappx.json
@@ -12,6 +12,10 @@
"type": "string",
"description": "An ID which identifies the adunit"
},
+ "mktag": {
+ "type": "string",
+ "description": "Minimum bid for this impression expressed in CPM (USD)"
+ },
"endpoint": {
"type": "string",
"description": "Endpoint provided to publisher"
@@ -19,6 +23,20 @@
"bidfloor": {
"type": "number",
"description": "Minimum bid for this impression expressed in CPM (USD)"
+ },
+ "bcid": {
+ "type": "array",
+ "description": "Block list of CID",
+ "items": {
+ "type": "string"
+ }
+ },
+ "bcrid": {
+ "type": "array",
+ "description": "Block list of CRID",
+ "items": {
+ "type": "string"
+ }
}
},
"required": ["host","tappxkey","endpoint"]
diff --git a/static/bidder-params/viewdeos.json b/static/bidder-params/viewdeos.json
new file mode 100644
index 00000000000..3e309e4b77a
--- /dev/null
+++ b/static/bidder-params/viewdeos.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Viewdeos Adapter Params",
+ "description": "A schema which validates params accepted by the Viewdeos adapter",
+
+ "type": "object",
+ "properties": {
+ "placementId": {
+ "type": "integer",
+ "description": "An ID which identifies this placement of the impression"
+ },
+ "siteId": {
+ "type": "integer",
+ "description": "An ID which identifies the site selling the impression"
+ },
+ "aid": {
+ "type": "integer",
+ "description": "An ID which identifies the channel"
+ },
+ "bidFloor": {
+ "type": "number",
+ "description": "BidFloor, US Dollars"
+ }
+ },
+ "required": ["aid"]
+}
diff --git a/static/tcf1/fallback_gvl.json b/static/tcf1/fallback_gvl.json
deleted file mode 100644
index 9f1c8506b32..00000000000
--- a/static/tcf1/fallback_gvl.json
+++ /dev/null
@@ -1 +0,0 @@
-{"vendorListVersion":215,"lastUpdated":"2020-08-13T16:00:19Z","purposes":[{"id":1,"name":"Information storage and access","description":"The storage of information, or access to information that is already stored, on your device such as advertising identifiers, device identifiers, cookies, and similar technologies."},{"id":2,"name":"Personalisation","description":"The collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as on other websites or apps, over time. Typically, the content of the site or app is used to make inferences about your interests, which inform future selection of advertising and/or content."},{"id":3,"name":"Ad selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver advertisements for you, and to measure the delivery and effectiveness of such advertisements. This includes using previously collected information about your interests to select ads, processing data about what advertisements were shown, how often they were shown, when and where they were shown, and whether you took any action related to the advertisement, including for example clicking an ad or making a purchase. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as websites or apps, over time."},{"id":4,"name":"Content selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver content for you, and to measure the delivery and effectiveness of such content. This includes using previously collected information about your interests to select content, processing data about what content was shown, how often or how long it was shown, when and where it was shown, and whether the you took any action related to the content, including for example clicking on content. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, such as websites or apps, over time."},{"id":5,"name":"Measurement","description":"The collection of information about your use of the content, and combination with previously collected information, used to measure, understand, and report on your usage of the service. This does not include personalisation, the collection of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, i.e. on other service, such as websites or apps, over time."}],"features":[{"id":1,"name":"Matching Data to Offline Sources","description":"Combining data from offline sources that were initially collected in other contexts."},{"id":2,"name":"Linking Devices","description":"Allow processing of a user's data to connect such user across multiple devices."},{"id":3,"name":"Precise Geographic Location Data","description":"Allow processing of a user's precise geographic location data in support of a purpose for which that certain third party has consent."}],"vendors":[{"id":8,"name":"Emerse Sverige AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.emerse.com/privacy-policy/"},{"id":9,"name":"AdMaxim Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.admaxim.com/admaxim-privacy-policy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":12,"name":"BeeswaxIO Corporation","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beeswax.com/privacy/"},{"id":28,"name":"TripleLift, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://triplelift.com/privacy/"},{"id":27,"name":"ADventori SAS","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adventori.com/with-us/legal-notice/"},{"id":25,"name":"Verizon Media EMEA Limited","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.verizonmedia.com/policies/ie/en/verizonmedia/privacy/index.html"},{"id":26,"name":"Venatus Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.venatusmedia.com/privacy/"},{"id":1,"name":"Exponential Interactive, Inc d/b/a VDX.tv","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://vdx.tv/privacy/"},{"id":6,"name":"AdSpirit GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adspirit.de/privacy"},{"id":30,"name":"BidTheatre AB","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.bidtheatre.com/privacy-policy"},{"id":24,"name":"Epsilon","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.conversantmedia.eu/legal/privacy-policy"},{"id":29,"name":"Etarget SE","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.etarget.sk/privacy.php","deletedDate":"2020-06-01T00:00:00Z"},{"id":39,"name":"ADITION technologies AG","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.adition.com/datenschutz"},{"id":11,"name":"Quantcast International Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.quantcast.com/privacy/"},{"id":15,"name":"Adikteev","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adikteev.com/privacy-policy-eng/"},{"id":4,"name":"Roq.ad Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.roq.ad/privacy-policy"},{"id":7,"name":"Vibrant Media Limited","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vibrantmedia.com/en/privacy-policy/"},{"id":2,"name":"Captify Technologies Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.captify.co.uk/privacy-policy/"},{"id":37,"name":"NEURAL.ONE","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://web.neural.one/privacy-policy/"},{"id":13,"name":"Sovrn Holdings Inc","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sovrn.com/sovrn-privacy/"},{"id":34,"name":"NEORY GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.neory.com/privacy.html"},{"id":32,"name":"Xandr, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.xandr.com/privacy/platform-privacy-policy/"},{"id":10,"name":"Index Exchange, Inc. ","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.indexexchange.com/privacy"},{"id":57,"name":"ADARA MEDIA UNLIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://adara.com/privacy-promise/"},{"id":63,"name":"Avocet Systems Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://avocet.io/privacy-portal"},{"id":51,"name":"xAd, Inc. dba GroundTruth","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.groundtruth.com/privacy-policy/"},{"id":49,"name":"TRADELAB","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://tradelab.com/en/privacy/"},{"id":45,"name":"Smart Adserver","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://smartadserver.com/end-user-privacy-policy/"},{"id":52,"name":"The Rubicon Project, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[3],"policyUrl":"http://www.rubiconproject.com/rubicon-project-yield-optimization-privacy-policy/"},{"id":71,"name":"Roku Advertising Services","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://docs.roku.com/published/userprivacypolicy/en/us"},{"id":79,"name":"MediaMath, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.mediamath.com/privacy-policy/"},{"id":91,"name":"Criteo SA","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.criteo.com/privacy/"},{"id":85,"name":"Crimtan Holdings Limited","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[1,3],"policyUrl":"https://crimtan.com/privacy/"},{"id":16,"name":"RTB House S.A.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.rtbhouse.com/privacy-center/services-privacy-policy/"},{"id":86,"name":"Scene Stealer Limited","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"http://scenestealer.tv/privacy-policy/"},{"id":94,"name":"Blis Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.blis.com/privacy/"},{"id":73,"name":"Simplifi Holdings Inc.","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2,3],"policyUrl":"https://simpli.fi/site-privacy-policy/"},{"id":33,"name":"ShareThis, Inc","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://sharethis.com/privacy/"},{"id":20,"name":"N Technologies Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://n.rich/privacy-notice"},{"id":55,"name":"Madison Logic, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.madisonlogic.com/privacy/"},{"id":53,"name":"Sirdata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.sirdata.com/privacy/"},{"id":69,"name":"OpenX","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.openx.com/legal/privacy-policy/"},{"id":98,"name":"GroupM UK Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.groupm.com/privacy-notice"},{"id":62,"name":"Justpremium BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://justpremium.com/privacy-policy/"},{"id":19,"name":"Intent Media, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://intentmedia.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":43,"name":"Vdopia DBA Chocolate Platform","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://chocolateplatform.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":36,"name":"RhythmOne DBA Unruly Group Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.rhythmone.com/privacy-policy"},{"id":80,"name":"Sharethrough, Inc","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://platform-cdn.sharethrough.com/privacy-policy"},{"id":81,"name":"PulsePoint, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pulsepoint.com/privacy-policy/website","deletedDate":"2020-07-06T00:00:00Z"},{"id":23,"name":"Amobee, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.amobee.com/trust/privacy-guidelines"},{"id":35,"name":"Purch Group, Inc.","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://www.purch.com/privacy-policy/","deletedDate":"2019-05-30T00:00:00Z"},{"id":3,"name":"affilinet","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.affili.net/de/footeritem/datenschutz","deletedDate":"2019-06-21T00:00:00Z"},{"id":74,"name":"Admotion SRL","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.admotion.com/policy/","deletedDate":"2019-07-24T00:00:00Z"},{"id":191,"name":"realzeit GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://realzeitmedia.com/privacy.html","deletedDate":"2019-04-29T00:00:00Z"},{"id":197,"name":"Switch Concepts Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.switchconcepts.com/privacy-policy","deletedDate":"2019-07-26T00:00:00Z"},{"id":390,"name":"Parsec Media Inc.","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,3],"policyUrl":"www.parsec.media/privacy-policy","deletedDate":"2019-06-27T00:00:00Z"},{"id":459,"name":"uppr GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://netzwerk.uppr.de/privacy-policy.do","deletedDate":"2019-06-17T00:00:00Z"},{"id":221,"name":"LEMO MEDIA GROUP LIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.lemomedia.com/terms.pdf","deletedDate":"2019-06-28T00:00:00Z"},{"id":478,"name":"RevLifter Ltd","purposeIds":[1],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.revlifter.com/privacy-policy","deletedDate":"2019-07-15T00:00:00Z"},{"id":500,"name":"Turbo","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.turboadv.com/white-rabbit-privacy-policy/","deletedDate":"2019-07-12T00:00:00Z"},{"id":68,"name":"Sizmek by Amazon","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.sizmek.com/privacy-policy/"},{"id":75,"name":"M32 Connect Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://m32.media/privacy-cookie-policy/"},{"id":17,"name":"Greenhouse Group BV (with its trademark LemonPI)","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.lemonpi.io/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":61,"name":"GumGum, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://gumgum.com/privacy-policy"},{"id":40,"name":"Active Agent (ADITION technologies AG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.active-agent.com/de/unternehmen/datenschutzerklaerung/"},{"id":76,"name":"PubMatic, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://pubmatic.com/privacy-policy/"},{"id":89,"name":"Tapad, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.tapad.com/eu-privacy-policy"},{"id":46,"name":"Skimbit Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://skimlinks.com/pages/privacy-policy"},{"id":66,"name":"adsquare GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adsquare.com/privacy"},{"id":105,"name":"Impression Desk Technologies Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://impressiondesk.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":41,"name":"Adverline","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.adverline.com/privacy/"},{"id":82,"name":"Smaato, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.smaato.com/privacy/"},{"id":60,"name":"Rakuten Marketing LLC","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://rakutenadvertising.com/legal-notices/services-privacy-policy/"},{"id":70,"name":"Yieldlab AG","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[3],"policyUrl":"http://www.yieldlab.de/meta-navigation/datenschutz/"},{"id":50,"name":"Adform","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://site.adform.com/privacy-center/platform-privacy/product-and-services-privacy-policy/"},{"id":48,"name":"NetSuccess, s.r.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inres.sk/pp/"},{"id":100,"name":"Fifty Technology Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://fifty.io/privacy-policy.php"},{"id":21,"name":"The Trade Desk","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.thetradedesk.com/general/privacy-policy"},{"id":110,"name":"Dynata LLC","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.opinionoutpost.co.uk/en-gb/policies/privacy"},{"id":42,"name":"Taboola Europe Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.taboola.com/privacy-policy"},{"id":112,"name":"Maytrics GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://maytrics.com/privacy.php","deletedDate":"2019-09-17T00:00:00Z"},{"id":77,"name":"comScore, Inc.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.scorecardresearch.com/privacy.aspx?newlanguage=1"},{"id":109,"name":"LoopMe Limited","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://loopme.com/privacy-policy/"},{"id":120,"name":"Eyeota Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.eyeota.com/privacy-center"},{"id":93,"name":"Adloox SA","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://adloox.com/disclaimer"},{"id":132,"name":"Teads ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.teads.com/privacy-policy/"},{"id":22,"name":"admetrics GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://admetrics.io/en/privacy_policy/"},{"id":102,"name":"Telaria SAS","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":108,"name":"Rich Audience Technologies SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://richaudience.com/privacy/"},{"id":18,"name":"Widespace AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.widespace.com/legal/privacy-policy-notice/"},{"id":122,"name":"Avid Media Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.avidglobalmedia.eu/privacy-policy.html"},{"id":97,"name":"LiveRamp, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.liveramp.com/service-privacy-policy/"},{"id":138,"name":"ConnectAd Realtime GmbH","purposeIds":[1,2],"legIntPurposeIds":[3,4],"featureIds":[],"policyUrl":"http://connectadrealtime.com/privacy/"},{"id":72,"name":"Nano Interactive GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.nanointeractive.com/privacy"},{"id":127,"name":"PIXIMEDIA SAS","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://piximedia.com/privacy/"},{"id":136,"name":"Str\u00f6er SSP GmbH (SSP)","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[2,3],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":111,"name":"Showheroes SE","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://showheroes.com/privacy/"},{"id":56,"name":"Confiant Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.confiant.com/privacy","deletedDate":"2020-05-18T00:00:00Z"},{"id":124,"name":"Teemo SA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://teemo.co/fr/confidentialite/"},{"id":154,"name":"YOC AG","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://yoc.com/privacy/"},{"id":38,"name":"Beemray Oy","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beemray.com/privacy-policy/","deletedDate":"2020-06-19T00:00:00Z"},{"id":101,"name":"MiQ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://wearemiq.com/privacy-policy/"},{"id":149,"name":"ADman Interactive SLU","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://admanmedia.com/politica.html?setLng=es"},{"id":151,"name":"Admedo Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[3],"policyUrl":"https://www.admedo.com/privacy-policy","deletedDate":"2020-07-17T00:00:00Z"},{"id":153,"name":"MADVERTISE MEDIA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://madvertise.com/en/gdpr/"},{"id":159,"name":"Underdog Media LLC ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://underdogmedia.com/privacy-policy/"},{"id":157,"name":"Seedtag Advertising S.L","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.seedtag.com/en/privacy-policy/"},{"id":145,"name":"Snapsort Inc., operating as Sortable","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://help.sortable.com/help/privacy-policy"},{"id":131,"name":"ID5 Technology SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.id5.io/privacy"},{"id":158,"name":"Reveal Mobile, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://revealmobile.com/privacy"},{"id":147,"name":"Adacado Technologies Inc. (DBA Adacado)","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adacado.com/privacy-policy-april-25-2018/"},{"id":130,"name":"NextRoll, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.nextroll.com/privacy"},{"id":129,"name":"IPONWEB GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.iponweb.com/privacy-policy/"},{"id":128,"name":"BIDSWITCH GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bidswitch.com/privacy-policy/"},{"id":168,"name":"EASYmedia GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://login.rtbmarket.com/gdpr"},{"id":164,"name":"Outbrain UK Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.outbrain.com/legal/privacy#privacy-policy"},{"id":144,"name":"district m inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://districtm.net/en/page/platforms-data-and-privacy-policy/"},{"id":163,"name":"Bombora Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://bombora.com/privacy"},{"id":173,"name":"Yieldmo, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.yieldmo.com/privacy/"},{"id":88,"name":"TreSensa, Inc.","purposeIds":[1,3],"legIntPurposeIds":[2,5],"featureIds":[1],"policyUrl":"https://www.tresensa.com/eu-privacy"},{"id":78,"name":"Flashtalking, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.flashtalking.com/privacypolicy/"},{"id":59,"name":"Sift Media, Inc","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.sift.co/privacy"},{"id":114,"name":"Sublime","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://ayads.co/privacy.php"},{"id":175,"name":"FORTVISION","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://fortvision.com/POC/index.html","deletedDate":"2019-08-09T00:00:00Z"},{"id":133,"name":"digitalAudience","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://digitalaudience.io/legal/privacy-cookies/"},{"id":14,"name":"Adkernel LLC","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://adkernel.com/privacy-policy/"},{"id":180,"name":"Thirdpresence Oy","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"http://www.thirdpresence.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":183,"name":"EMX Digital LLC","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://emxdigital.com/privacy/"},{"id":58,"name":"33Across","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.33across.com/privacy-policy"},{"id":140,"name":"Platform161","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://platform161.com/cookie-and-privacy-policy/"},{"id":90,"name":"Teroa S.A.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.e-planning.net/en/privacy.html"},{"id":141,"name":"1020, Inc. dba Placecast and Ericsson Emodo","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.emodoinc.com/privacy-policy/"},{"id":142,"name":"Media.net Advertising FZ-LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.media.net/en/privacy-policy"},{"id":209,"name":"Delta Projects AB","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[3],"policyUrl":"https://deltaprojects.com/data-collection-policy"},{"id":195,"name":"advanced store GmbH","purposeIds":[2,3],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.advanced-store.com/de/datenschutz/"},{"id":190,"name":"video intelligence AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.vi.ai/privacy-policy/"},{"id":84,"name":"Semasio GmbH","purposeIds":[],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"http://www.semasio.com/privacy-policy/"},{"id":65,"name":"Location Sciences AI Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.locationsciences.ai/privacy-policy/"},{"id":210,"name":"Zemanta, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1],"policyUrl":"http://www.zemanta.com/legal/privacy"},{"id":200,"name":"Tapjoy, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.tapjoy.com/legal/#privacy-policy"},{"id":188,"name":"Sellpoints Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://retargeter.com/service-privacy-policy/","deletedDate":"2019-09-17T00:00:00Z"},{"id":217,"name":"2KDirect, Inc. (dba iPromote)","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.ipromote.com/privacy-policy/"},{"id":156,"name":"Centro, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.centro.net/privacy-policy/"},{"id":194,"name":"Rezonence Limited","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://rezonence.com/privacy-policy/"},{"id":226,"name":"Publicis Media GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.publicismedia.de/datenschutz/"},{"id":198,"name":"SYNC","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://redirect.sync.tv/privacy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":227,"name":"ORTEC B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.ortecadscience.com/privacy-policy/"},{"id":225,"name":"Ligatus GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.ligatus.com/en/privacy-policy","deletedDate":"2020-06-19T00:00:00Z"},{"id":205,"name":"Adssets AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://adssets.com/policy/"},{"id":179,"name":"Collective Europe Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.collectiveuk.com/privacy.html"},{"id":31,"name":"Ogury Ltd.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://www.ogury.com/privacy-policy/"},{"id":92,"name":"1plusX AG","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.1plusx.com/privacy-policy/"},{"id":155,"name":"AntVoice","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.antvoice.com/en/privacypolicy/"},{"id":115,"name":"smartclip Europe GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://privacy-portal.smartclip.net/"},{"id":126,"name":"DoubleVerify Inc.\u200b","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.doubleverify.com/privacy/"},{"id":193,"name":"Mediasmart Mobile S.L.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://mediasmart.io/privacy/"},{"id":245,"name":"IgnitionOne","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.ignitionone.com/privacy-policy/","deletedDate":"2020-06-30T00:00:00Z"},{"id":213,"name":"emetriq GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.emetriq.com/datenschutz/"},{"id":244,"name":"Temelio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://temelio.com/vie-privee"},{"id":224,"name":"adrule mobile GmbH","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.adrule.net/de/datenschutz/"},{"id":174,"name":"A Million Ads Ltd","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.amillionads.com/privacy-policy"},{"id":192,"name":"remerge GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://remerge.io/privacy-policy.html"},{"id":232,"name":"Rockerbox, Inc","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"http://rockerbox.com/privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":256,"name":"Bounce Exchange, Inc","purposeIds":[1],"legIntPurposeIds":[2,4,5],"featureIds":[1,2],"policyUrl":"https://www.bouncex.com/privacy/"},{"id":234,"name":"ZBO Media","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zbo.media/mentions-legales/politique-de-confidentialite-service-publicitaire/"},{"id":246,"name":"Smartology Limited","purposeIds":[3],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://www.smartology.net/privacy-policy/"},{"id":241,"name":"OneTag Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.onetag.com/privacy/"},{"id":254,"name":"LiquidM Technology GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liquidm.com/privacy-policy/"},{"id":215,"name":"ARMIS SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://armis.tech/en/armis-personal-data-privacy-policy/"},{"id":167,"name":"Audiens S.r.l.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.audiens.com/privacy"},{"id":240,"name":"7Hops.com Inc. (ZergNet)","purposeIds":[],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://zergnet.com/privacy"},{"id":235,"name":"Bucksense Inc","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.bucksense.com/platform-privacy-policy/"},{"id":185,"name":"Bidtellect, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.bidtellect.com/privacy-policy/"},{"id":258,"name":"Adello Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.adello.com/privacy-policy/"},{"id":169,"name":"RTK.IO, Inc","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://www.rtk.io/privacy.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":208,"name":"Spotad","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.spotad.co/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":211,"name":"AdTheorent, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://adtheorent.com/privacy-policy"},{"id":229,"name":"Digitize New Media Ltd","purposeIds":[2,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitize.ie/online-privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":273,"name":"Bannerflow AB","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.bannerflow.com/privacy "},{"id":104,"name":"Sonobi, Inc","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"http://sonobi.com/privacy-policy/"},{"id":162,"name":"Unruly Group Ltd","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://unruly.co/privacy/"},{"id":249,"name":"Spolecznosci Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.spolecznosci.pl/polityka-prywatnosci"},{"id":125,"name":"Research Now Group, Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.valuedopinions.co.uk/privacy","deletedDate":"2019-09-17T00:00:00Z"},{"id":170,"name":"Goodway Group, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://goodwaygroup.com/privacy-policy/"},{"id":160,"name":"Netsprint SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://netsprint.eu/privacy.html"},{"id":189,"name":"Intowow Innovation Ltd.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.intowow.com/privacy/","deletedDate":"2019-08-12T00:00:00Z"},{"id":279,"name":"Mirando GmbH & Co KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://wwwmirando.de/datenschutz/"},{"id":269,"name":"Sanoma Media Finland","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://sanoma.fi/tietoa-meista/tietosuoja/","deletedDate":"2019-08-07T00:00:00Z"},{"id":276,"name":"Viralize SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://viralize.com/privacy-policy"},{"id":87,"name":"Genius Sports Media Limited","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[2,3],"policyUrl":"https://www.geniussports.com/privacy-policy"},{"id":182,"name":"Collective, Inc. dba Visto","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vistohub.com/privacy-policy/","deletedDate":"2019-07-26T00:00:00Z"},{"id":255,"name":"Onnetwork Sp. z o.o.","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.onnetwork.tv/pp_services.php"},{"id":203,"name":"Revcontent, LLC","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://intercom.help/revcontent2/en/articles/2290675-revcontent-s-privacy-policy"},{"id":260,"name":"RockYou, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,5],"featureIds":[3],"policyUrl":"https://rockyou.com/privacy-policy/","deletedDate":"2019-08-09T00:00:00Z"},{"id":237,"name":"LKQD, a division of Nexstar Digital, LLC.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.lkqd.com/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":274,"name":"Golden Bees","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.goldenbees.fr/en/privacy-charter/"},{"id":280,"name":"Spot.IM LTD","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.spot.im/privacy/"},{"id":239,"name":"Triton Digital Canada Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.tritondigital.com/privacy-policies"},{"id":177,"name":"plista GmbH","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.plista.com/about/privacy/"},{"id":201,"name":"TimeOne","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://privacy.timeonegroup.com/en/","deletedDate":"2020-05-15T00:00:00Z"},{"id":150,"name":"Inskin Media LTD","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.inskinmedia.com/privacy-policy.html"},{"id":252,"name":"Jaduda GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.jadudamobile.com/datenschutzerklaerung/"},{"id":248,"name":"Converge-Digital","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://converge-digital.com/privacy-policy/"},{"id":161,"name":"Smadex SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://smadex.com/end-user-privacy-policy/"},{"id":285,"name":"Comcast International France SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.freewheel.com/privacy-policy"},{"id":228,"name":"McCann Discipline LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.primis.tech/privacy-policy/"},{"id":299,"name":"AdClear GmbH","purposeIds":[1,5],"legIntPurposeIds":[2,3,4],"featureIds":[1,2],"policyUrl":"https://www.adclear.de/datenschutzerklaerung/"},{"id":277,"name":"Codewise VL Sp. z o.o. Sp. k","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://voluumdsp.com/end-user-privacy-policy/"},{"id":259,"name":"ADYOULIKE SA","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.adyoulike.com/privacy_policy.php"},{"id":272,"name":"A.Mob","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.we-are-adot.com/privacy-policy/"},{"id":230,"name":"Steel House, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://steelhouse.com/privacy-policy/"},{"id":253,"name":"Improve Digital BV","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.improvedigital.com/platform-privacy-policy"},{"id":304,"name":"On Device Research Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://s.on-device.com/privacyPolicy"},{"id":314,"name":"Keymantics","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.keymantics.com/assets/privacy-policy.pdf"},{"id":257,"name":"R-TARGET","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"http://www.r-target.com/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":317,"name":"mainADV Srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.mainad.com/privacy-policy/"},{"id":278,"name":"Integral Ad Science, Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://integralads.com/privacy-policy/"},{"id":291,"name":"Qwertize","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.qwertize.com/en/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":295,"name":"Sojern, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.sojern.com/privacy/product-privacy-policy/"},{"id":315,"name":"Celtra, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.celtra.com/privacy-policy/"},{"id":165,"name":"SpotX, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.spotx.tv/privacy-policy/"},{"id":47,"name":"ADMAN - Phaistos Networks, S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adman.gr/privacy"},{"id":134,"name":"SMARTSTREAM.TV GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://www.smartstream.tv/en/productprivacy"},{"id":325,"name":"Knorex","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.knorex.com/privacy"},{"id":316,"name":"Gamned","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.gamned.com/privacy-policy/"},{"id":318,"name":"Accorp Sp. z o.o.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://www.instytut-pollster.pl/privacy-policy/"},{"id":199,"name":"ADUX","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adux.com/donnees-personelles/"},{"id":236,"name":"PowerLinks Media Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[3],"policyUrl":"https://www.powerlinks.com/privacy-policy/"},{"id":294,"name":"Jivox Corporation","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.jivox.com/privacy"},{"id":143,"name":"Connatix Native Exchange Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://connatix.com/privacy-policy/"},{"id":297,"name":"Polar Mobile Group Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://privacy.polar.me"},{"id":319,"name":"Clipcentric, Inc.","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://clipcentric.com/privacy.bhtml"},{"id":290,"name":"Readpeak Oy","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://readpeak.com/privacy-policy/"},{"id":323,"name":"DAZN Media Services Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.goal.com/en-gb/legal/privacy-policy"},{"id":119,"name":"Fusio by S4M","purposeIds":[1,2,5],"legIntPurposeIds":[3],"featureIds":[1,3],"policyUrl":"http://www.s4m.io/privacy-policy/"},{"id":302,"name":"Mobile Professionals BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mobpro.com/privacy.html"},{"id":212,"name":"usemax advertisement (Emego GmbH)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.usemax.de/?l=privacy"},{"id":264,"name":"Adobe Advertising Cloud","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.adobe.com/privacy/experience-cloud.html"},{"id":44,"name":"The ADEX GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://theadex.com/privacy-opt-out/"},{"id":282,"name":"Welect GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.welect.de/datenschutz"},{"id":238,"name":"StackAdapt","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.stackadapt.com/privacy"},{"id":284,"name":"WEBORAMA","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://weborama.com/privacy_en/"},{"id":148,"name":"Liveintent Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://liveintent.com/services-privacy-policy/"},{"id":64,"name":"DigiTrust / IAB Tech Lab","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitru.st/privacy-policy/"},{"id":301,"name":"zeotap GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://zeotap.com/privacy_policy"},{"id":275,"name":"TabMo SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://static.tabmo.io.s3.amazonaws.com/privacy-policy/index.html"},{"id":310,"name":"Adevinta Spain S.L.U.","purposeIds":[],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"https://www.adevinta.com/about/privacy/"},{"id":139,"name":"Permodo GmbH","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://permodo.com/de/privacy.html"},{"id":326,"name":"AdTiming Technology Company Limited","purposeIds":[3,5],"legIntPurposeIds":[1,2,4],"featureIds":[],"policyUrl":"http://www.adtiming.com/en/privacypolicy.html"},{"id":262,"name":"Fyber ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.fyber.com/legal/privacy-policy/"},{"id":331,"name":"ad6media","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.ad6media.fr/privacy"},{"id":345,"name":"The Kantar Group Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.kantar.com/cookies-policies"},{"id":308,"name":"Rockabox Media Ltd","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[],"policyUrl":"http://scoota.com/privacy-policy"},{"id":270,"name":"Marfeel Solutions, SL","purposeIds":[],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.marfeel.com/privacy-policy/"},{"id":333,"name":"InMobi Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":202,"name":"Telaria, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":328,"name":"Gemius SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.gemius.com/cookie-policy.html"},{"id":281,"name":"Wizaly","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.wizaly.com/terms-of-use#privacy-policy"},{"id":354,"name":"Apester Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://apester.com/privacy-policy/"},{"id":320,"name":"Adelphic LLC","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://adelphic.com/platform/privacy/"},{"id":359,"name":"AerServ LLC","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":265,"name":"Instinctive, Inc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://instinctive.io/privacy"},{"id":349,"name":"Optomaton UG","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://optomaton.com/privacy.html"},{"id":288,"name":"Video Media Groep B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://www.videomediagroup.com/wp-content/uploads/2016/01/Privacy-policy-VMG.pdf","deletedDate":"2019-09-17T00:00:00Z"},{"id":266,"name":"Digilant Spain, SLU","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.digilant.com/es/politica-privacidad/"},{"id":339,"name":"Vuble","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vuble.tv/us/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":303,"name":"Orion Semantics","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://static.orion-semantics.com/privacy.html"},{"id":261,"name":"Signal Digital Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.signal.co/privacy-policy/"},{"id":83,"name":"Visarity Technologies GmbH","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://primo.design/docs/PrivacyPolicyPrimo.html"},{"id":343,"name":"DIGITEKA Technologies","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.ultimedia.com/POLICY.html"},{"id":330,"name":"Linicom","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.linicom.com/privacy/","deletedDate":"2020-06-08T00:00:00Z"},{"id":231,"name":"AcuityAds Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.acuityads.com/corporate-privacy-policy.html"},{"id":216,"name":"Mindlytix SAS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://mindlytix.com/privacy/"},{"id":311,"name":"Mobfox US LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobfox.com/privacy-policy/"},{"id":358,"name":"MGID Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mgid.com/privacy-policy"},{"id":152,"name":"Meetrics GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.meetrics.com/en/data-privacy/"},{"id":251,"name":"Yieldlove GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"http://www.yieldlove.com/cookie-policy"},{"id":344,"name":"My6sense Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[2,4],"featureIds":[],"policyUrl":"https://my6sense.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":347,"name":"Ezoic Inc.","purposeIds":[2,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.ezoic.com/terms/"},{"id":218,"name":"Bigabid Media ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.bigabid.com/privacy-policy"},{"id":350,"name":"Free Stream Media Corp. dba Samba TV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":351,"name":"Samba TV UK Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":341,"name":"Somo Audience Corp","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"https://somoaudience.com/legal/","deletedDate":"2020-07-06T00:00:00Z"},{"id":380,"name":"Vidoomy Media SL","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"http://vidoomy.com/privacy-policy.html"},{"id":378,"name":"communicationAds GmbH & Co. KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.communicationads.net/aboutus/privacy/"},{"id":369,"name":"Getintent USA, inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://getintent.com/privacy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":184,"name":"mediarithmics SAS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mediarithmics.com/en-us/content/privacy-policy"},{"id":368,"name":"VECTAURY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vectaury.io/en/personal-data"},{"id":373,"name":"Nielsen Marketing Cloud","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"http://www.nielsen.com/us/en/privacy-statement/exelate-privacy-policy.html"},{"id":214,"name":"Digital Control GmbH & Co. KG","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://advolution.de/privacy.php","deletedDate":"2020-05-06T00:00:00Z"},{"id":388,"name":"numberly","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://numberly.com/en/privacy/"},{"id":250,"name":"Qriously Ltd","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.brandwatch.com/legal/qriously-privacy-notice/"},{"id":223,"name":"Audience Trading Platform Ltd.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://atp.io/privacy-policy"},{"id":387,"name":"Triapodi Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appreciate.mobi/page.html#/end-user-privacy-policy"},{"id":312,"name":"Exactag GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.exactag.com/en/data-privacy/"},{"id":178,"name":"Hybrid Theory","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://hybridtheory.com/privacy-policy/"},{"id":377,"name":"AddApptr GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.addapptr.com/data-privacy"},{"id":382,"name":"The Reach Group GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://trg.de/en/privacy-statement/"},{"id":206,"name":"Hybrid Adtech GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://hybrid.ai/data_protection_policy"},{"id":403,"name":"Mobusi Mobile Advertising S.L.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobusi.com/privacy.en.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":385,"name":"Oracle Data Cloud","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://www.oracle.com/legal/privacy/marketing-cloud-data-cloud-privacy-policy.html"},{"id":404,"name":"Duplo Media AS","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.easy-ads.com/privacypolicy.htm"},{"id":242,"name":"twiago GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.twiago.com/datenschutz/"},{"id":376,"name":"Pocketmath Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pocketmath.com/privacy-policy"},{"id":402,"name":"Effiliation","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://inter.effiliation.com/politique-confidentialite.html"},{"id":413,"name":"Eulerian Technologies","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.eulerian.com/en/privacy/"},{"id":400,"name":"Whenever Media Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.whenevermedia.com/privacy-policy","deletedDate":"2019-07-29T00:00:00Z"},{"id":171,"name":"Webedia","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webedia-group.com/site/privacy-policy","deletedDate":"2020-07-01T00:00:00Z"},{"id":398,"name":"Yormedia Solutions Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.yormedia.com/privacy-and-cookies-notice/","deletedDate":"2019-08-06T00:00:00Z"},{"id":415,"name":"Seenthis AB","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://seenthis.co/privacy-notice-2018-04-18.pdf"},{"id":263,"name":"Nativo, Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.nativo.com/interest-based-ads"},{"id":329,"name":"Browsi Mobile Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://gobrowsi.com/browsi-privacy-policy/"},{"id":389,"name":"Bidmanagement GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adspert.net/en/privacy/","deletedDate":"2020-07-01T00:00:00Z"},{"id":337,"name":"SheMedia, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shemedia.com/ad-services-privacy-policy"},{"id":422,"name":"Brand Metrics Sweden AB","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://collector.brandmetrics.com/brandmetrics_privacypolicy.pdf"},{"id":421,"name":"LeftsnRight, Inc. dba LIQWID","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liqwid.solutions/privacy-policy","deletedDate":"2020-06-30T00:00:00Z"},{"id":426,"name":"TradeTracker","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[2],"policyUrl":"https://tradetracker.com/privacy-policy/","deletedDate":"2019-08-21T00:00:00Z"},{"id":394,"name":"AudienceProject Aps","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://privacy.audienceproject.com"},{"id":287,"name":"Avazu Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4],"featureIds":[3],"policyUrl":"http://avazuinc.com/opt-out/","deletedDate":"2020-08-03T00:00:00Z"},{"id":243,"name":"Cloud Technologies S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cloudtechnologies.pl/en/internet-advertising-privacy-policy"},{"id":113,"name":"iotec global Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.iotecglobal.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":338,"name":"dunnhumby Germany GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.sociomantic.com/privacy/en/","deletedDate":"2020-07-17T00:00:00Z"},{"id":405,"name":"IgnitionAi Ltd","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[2],"policyUrl":"https://www.isitelab.io/default.aspx","deletedDate":"2020-07-03T00:00:00Z"},{"id":416,"name":"Commanders Act","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.commandersact.com/en/privacy/"},{"id":434,"name":"DynAdmic","purposeIds":[1,3],"legIntPurposeIds":[2,4],"featureIds":[1,3],"policyUrl":"http://eu.dynadmic.com/privacy-policy/"},{"id":435,"name":"SINGLESPOT SAS ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.singlespot.com/privacy_policy?locale=fr"},{"id":409,"name":"Arrivalist Co.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[1,2],"policyUrl":"https://www.arrivalist.com/privacy"},{"id":321,"name":"Ziff Davis LLC","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.ziffdavis.com/privacy-policy"},{"id":436,"name":"INVIBES GROUP","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[1,2,3],"policyUrl":"http://www.invibes.com/terms"},{"id":442,"name":"R-Advertising","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-20T00:00:00Z"},{"id":362,"name":"Myntelligence S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://myntelligence.com/privacy-page/"},{"id":418,"name":"PROXISTORE","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://www.proxistore.com/common/en/cgv"},{"id":449,"name":"Mobile Journey B.V.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://mobilejourney.com/Privacy-Policy","deletedDate":"2019-09-05T00:00:00Z"},{"id":443,"name":"Tradedoubler AB","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-13T00:00:00Z"},{"id":429,"name":"Signals","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://signalsdata.com/platform-cookie-policy/"},{"id":335,"name":"Beachfront Media LLC","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://beachfront.com/privacy-policy/"},{"id":407,"name":"Publishers Internationale Pty Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pi-rate.com.au/privacy.html","deletedDate":"2019-11-08T00:00:00Z"},{"id":427,"name":"Proxi.cloud Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://proxi.cloud/info/privacy-policy/"},{"id":374,"name":"Bmind a Sales Maker Company, S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bmind.es/legal-notice/"},{"id":438,"name":"INVIDI technologies AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.invidi.com/wp-content/uploads/2020/02/ad-tech-services-privacy-policy.pdf"},{"id":450,"name":"Neodata Group srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.neodatagroup.com/en/security-policy"},{"id":452,"name":"Innovid Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.innovid.com/privacy-policy"},{"id":444,"name":"Playbuzz Ltd (aka EX.CO)","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://ex.co/privacy-policy/"},{"id":412,"name":"Cxense ASA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.cxense.com/about-us/privacy-policy"},{"id":454,"name":"Adimo","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://adimo.co/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":455,"name":"GDMServices, Inc. d/b/a FiksuDSP","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://fiksu.com/privacy-policy/"},{"id":298,"name":"Cuebiq Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.cuebiq.com/privacypolicy/","deletedDate":"2019-08-30T00:00:00Z"},{"id":423,"name":"travel audience GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://travelaudience.com/product-privacy-policy/"},{"id":397,"name":"Demandbase, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.demandbase.com/privacy-policy/"},{"id":381,"name":"Solocal","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://frontend.adhslx.com/privacy.html?"},{"id":425,"name":"ADRINO Sp. z o.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.adrino.pl/ciasteczkowa-polityka/","deletedDate":"2019-09-05T00:00:00Z"},{"id":365,"name":"Forensiq LLC","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1,3],"policyUrl":"https://impact.com/privacy-policy/"},{"id":447,"name":"Adludio Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adludio.com/privacy-policy/"},{"id":410,"name":"Adtelligent Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtelligent.com/privacy-policy/"},{"id":137,"name":"Str\u00f6er SSP GmbH (DSP)","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":395,"name":"PREX Programmatic Exchange GmbH&Co KG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[],"policyUrl":"http://www.programmatic-exchange.com/privacy","deletedDate":"2020-07-03T00:00:00Z"},{"id":462,"name":"Bidstack Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[2],"policyUrl":"https://www.bidstack.com/privacy-policy/"},{"id":466,"name":"TACTIC\u2122 Real-Time Marketing AS","purposeIds":[],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://tacticrealtime.com/privacy/"},{"id":340,"name":"Yieldr UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.yieldr.com/privacy"},{"id":336,"name":"Telecoming S.A.","purposeIds":[3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.telecoming.com/privacy-policy/"},{"id":430,"name":"Ad Unity Ltd","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"http://www.adunity.com/privacy-policy.html","deletedDate":"2019-08-13T00:00:00Z"},{"id":346,"name":"Cybba, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://cybba.com/about/legal/data-processing-agreement/","deletedDate":"2020-08-03T00:00:00Z"},{"id":469,"name":"Zeta Global","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://zetaglobal.com/privacy-policy/"},{"id":440,"name":"DEFINE MEDIA GMBH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.definemedia.de/datenschutz-conative/"},{"id":375,"name":"Affle International","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://affle.com/privacy-policy "},{"id":196,"name":"AdElement Media Solutions Pvt Ltd","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"http://adelement.com/privacy-policy.html"},{"id":268,"name":"Social Tokens Ltd. ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://woobi.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":475,"name":"TAPTAP Digital SL","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1,2,3],"policyUrl":"http://www.taptapnetworks.com/privacy_policy/"},{"id":474,"name":"hbfsTech","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.hbfstech.com/fr/privacy.html"},{"id":448,"name":"Targetspot Belgium SPRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://marketing.targetspot.com/Targetspot/Legal/TargetSpot%20Privacy%20Policy%20-%20June%202018.pdf"},{"id":428,"name":"Internet BillBoard a.s.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.ibillboard.com/en/privacy-information/"},{"id":461,"name":"B2B Media Group EMEA GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selfcampaign.com/static/privacy","deletedDate":"2019-08-14T00:00:00Z"},{"id":476,"name":"HIRO Media Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"http://hiro-media.com/privacy.php"},{"id":480,"name":"pilotx.tv","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[1,2,3],"policyUrl":"https://pilotx.tv/privacy/"},{"id":366,"name":"CerebroAd.com s.r.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.cerebroad.com/privacy-policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":392,"name":"Str\u00f6er Mobile Performance GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[3],"policyUrl":"https://stroeermobileperformance.com/?dl=privacy"},{"id":357,"name":"Totaljobs Group Ltd ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.totaljobs.com/privacy-policy"},{"id":486,"name":"Madington","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://delivered-by-madington.com/dat-privacy-policy/"},{"id":468,"name":"NeuStar, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://www.home.neustar/privacy"},{"id":458,"name":"AdColony, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"adcolony.com/privacy-policy/"},{"id":489,"name":"YellowHammer Media Group","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.yhmg.com/privacy-policy/","deletedDate":"2019-11-27T00:00:00Z"},{"id":293,"name":"SpringServe, LLC","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://springserve.com/privacy-policy/"},{"id":484,"name":"STRIATUM SAS","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://adledge.com/data-privacy/"},{"id":493,"name":"Carbon (AI) Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://carbonrmp.com/privacy.html"},{"id":495,"name":"Arcspire Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://public.arcspire.io/privacy.pdf"},{"id":496,"name":"Automattic Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://en.blog.wordpress.com/2017/12/04/updated-privacy-policy/"},{"id":424,"name":"KUPONA GmbH","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.kupona.de/dsgvo/"},{"id":408,"name":"Fidelity Media","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://fidelity-media.com/privacy-policy/"},{"id":473,"name":"Sub2 Technologies Ltd","purposeIds":[3,4,5],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.sub2tech.com/privacy-policy/"},{"id":467,"name":"Haensel AMS GmbH","purposeIds":[3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://haensel-ams.com/data-privacy/"},{"id":490,"name":"PLAYGROUND XYZ EMEA LTD","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://playground.xyz/privacy"},{"id":464,"name":"Oracle AddThis","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.addthis.com/privacy/privacy-policy/","deletedDate":"2020-02-12T00:00:00Z"},{"id":491,"name":"Triboo Data Analytics","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shinystat.com/it/informativa_privacy_generale.html"},{"id":499,"name":"PurposeLab, LLC","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://purposelab.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":502,"name":"NEXD","purposeIds":[5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://nexd.com/privacy-policy"},{"id":465,"name":"Schibsted Product and Tech UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.schibsted.com/","deletedDate":"2019-07-26T00:00:00Z"},{"id":497,"name":"Little Big Data sp.z.o.o.","purposeIds":[1,2,4],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://dtxngr.com/legal/"},{"id":492,"name":"LotaData, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1],"policyUrl":"https://lotadata.com/privacy_policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":512,"name":"PubNative GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://pubnative.net/privacy-notice/"},{"id":471,"name":"FlexOffers.com, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.flexoffers.com/privacy-policy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":494,"name":"Cablato Limited","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://cablato.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":516,"name":"Pexi B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://pexi.nl/privacy-policy/"},{"id":507,"name":"AdsWizz Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://www.adswizz.com/our-privacy-policy/"},{"id":482,"name":"UberMedia, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ubermedia.com/summary-of-privacy-policy/"},{"id":505,"name":"Shopalyst Inc","purposeIds":[1,2],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shortlyst.com/eu/privacy_terms.html"},{"id":517,"name":"SunMedia ","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2],"policyUrl":"https://www.sunmedia.tv/en/cookies"},{"id":518,"name":"Accelerize Inc.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://getcake.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":511,"name":"Admixer EU GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://admixer.com/privacy/"},{"id":479,"name":"INFINIA MOBILE S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.infiniamobile.com/privacy_policy"},{"id":513,"name":"Shopstyle","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shopstyle.co.uk/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":509,"name":"ATG Ad Tech Group GmbH","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ad-tech-group.com/privacy-policy/"},{"id":521,"name":"netzeffekt GmbH","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.netzeffekt.de/en/imprint"},{"id":487,"name":"nugg.ad GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1],"policyUrl":"https://www.nugg.ad/en/privacy/general-information.html","deletedDate":"2019-10-03T00:00:00Z"},{"id":515,"name":"ZighZag","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zighzag.com/privacy"},{"id":520,"name":"ChannelSight ","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.channelsight.com/privacypolicy/"},{"id":524,"name":"The Ozone Project Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://ozoneproject.com/privacy-policy"},{"id":529,"name":"Fidzup","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.fidzup.com/en/privacy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":528,"name":"Kayzen","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://kayzen.io/data-privacy-policy"},{"id":527,"name":"Jampp LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://jampp.com/privacy.html"},{"id":506,"name":"salesforce.com, inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.salesforce.com/company/privacy/"},{"id":534,"name":"SmartyAds Inc.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://smartyads.com/privacy-policy"},{"id":535,"name":"INNITY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.innity.com/privacy-policy.php"},{"id":514,"name":"Uprival LLC","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://uprival.com/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":522,"name":"Tealium Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://tealium.com/privacy-policy/"},{"id":530,"name":"Near Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://near.co/privacy"},{"id":539,"name":"AdDefend GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.addefend.com/en/privacy-policy/"},{"id":501,"name":"Alliance Gravity Data Media","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.alliancegravity.com/politiquedeprotectiondesdonneespersonnelles"},{"id":519,"name":"Chargeads","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.chargeplatform.com/privacy"},{"id":523,"name":"X-Mode Social, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://xmode.io/privacy-policy.html"},{"id":537,"name":"RUN, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.runads.com/privacy-policy"},{"id":531,"name":"Smartclip Hispania SL","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://rgpd-smartclip.com/"},{"id":536,"name":"GlobalWebIndex","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"http://legal.trendstream.net/non-panellist_privacy_policy"},{"id":542,"name":"Densou Trading Desk ApS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://densou.dk/Policy.html","deletedDate":"2020-01-21T00:00:00Z"},{"id":525,"name":"PUB OCEAN LIMITED","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://rta.pubocean.com/privacy-policy/","deletedDate":"2019-10-03T00:00:00Z"},{"id":544,"name":"Kochava Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://www.kochava.com/support-privacy/"},{"id":543,"name":"PaperG, Inc. dba Thunder Industries","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.makethunder.com/privacy"},{"id":334,"name":"Cydersoft","purposeIds":[],"legIntPurposeIds":[1,2,3,4],"featureIds":[2,3],"policyUrl":"http://www.videmob.com/privacy.html"},{"id":551,"name":"Illuma Technology Limited","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.weareilluma.com/endddd","deletedDate":"2019-11-14T00:00:00Z"},{"id":540,"name":"Tunnl BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://tunnl.com/privacy.html","deletedDate":"2019-12-20T00:00:00Z"},{"id":547,"name":"Video Reach","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.videoreach.de/about/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":546,"name":"Smart Traffik","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://okube-attribution.com/politique-de-confidentialite/"},{"id":541,"name":"DeepIntent, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.deepintent.com/privacypolicy"},{"id":545,"name":"Reignn Platform Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://reignn.com/user-privacy-policy"},{"id":439,"name":"Bit Q Holdings Limited","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.rippll.com/privacy"},{"id":553,"name":"Adhese","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://adhese.com/privacy-and-cookie-policy"},{"id":556,"name":"adhood.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://v3.adhood.com/en/site/politikavekurallar/gizlilik.php?lang=en"},{"id":550,"name":"Happydemics","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.iubenda.com/privacy-policy/69056167/full-legal"},{"id":560,"name":"Leiki Ltd.","purposeIds":[1,2,3],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"http://www.leiki.com/privacy","deletedDate":"2020-01-07T00:00:00Z"},{"id":554,"name":"RMSi Radio Marketing Service interactive GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.rms.de/datenschutz/"},{"id":498,"name":"Mediakeys Platform","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://drbanner.com/privacypolicy_en/"},{"id":565,"name":"Adobe Audience Manager","purposeIds":[1,2,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adobe.com/privacy/policy.html"},{"id":118,"name":"Drawbridge, Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.drawbridge.com/privacy/","deletedDate":"2020-03-06T00:00:00Z"},{"id":572,"name":"CHEQ AI TECHNOLOGIES LTD.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.cheq.ai/privacy"},{"id":571,"name":"ViewPay","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://viewpay.tv/mentions-legales/"},{"id":568,"name":"Jointag S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.jointag.com/privacy/kariboo/publisher/third/"},{"id":570,"name":"Czech Publisher Exchange z.s.p.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cpex.cz/pro-uzivatele/ochrana-soukromi/"},{"id":559,"name":"Otto (GmbH & Co KG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2],"policyUrl":"https://www.otto.de/shoppages/service/datenschutz"},{"id":548,"name":"LBC France","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.leboncoin.fr/dc/cookies","deletedDate":"2020-04-23T00:00:00Z"},{"id":569,"name":"Kairos Fire","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.kairosfire.com/privacy"},{"id":577,"name":"Neustar on behalf of The Procter & Gamble Company","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pg.com/privacy/english/privacy_statement.shtml"},{"id":590,"name":"Sourcepoint Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.sourcepoint.com/privacy-policy"},{"id":587,"name":"Localsensor B.V.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.localsensor.com/privacy.html"},{"id":578,"name":"MAIRDUMONT NETLETIX GmbH&Co. KG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mairdumont-netletix.com/datenschutz"},{"id":580,"name":"Goldbach Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://goldbach.com/ch/de/datenschutz"},{"id":593,"name":"Programatica de publicidad S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://datmean.com/politica-privacidad/"},{"id":574,"name":"Realeyes OU","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://realview.realeyesit.com/privacy"},{"id":581,"name":"Mobilewalla, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.mobilewalla.com/business-services-privacy-policy"},{"id":598,"name":"audio content & control GmbH","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://www.audio-cc.com/audiocc_privacy_policy.pdf"},{"id":596,"name":"InsurAds Technologies SA.","purposeIds":[3],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.insurads.com/privacy.html"},{"id":576,"name":"StartApp Inc.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://www.startapp.com/policy/privacy-policy/","deletedDate":"2020-04-23T00:00:00Z"},{"id":592,"name":"Colpirio.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy-policy.colpirio.com/en/","deletedDate":"2020-03-18T00:00:00Z"},{"id":549,"name":"Bandsintown Amplified LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://corp.bandsintown.com/privacy"},{"id":597,"name":"Better Banners A/S","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://betterbanners.com/en/privacy"},{"id":601,"name":"WebAds B.V","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.webads.eu/"},{"id":599,"name":"Maximus Live LLC","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://maximusx.com/privacy-policy/"},{"id":604,"name":"Join","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.teamjoin.fr/privacy.html","deletedDate":"2020-04-23T00:00:00Z"},{"id":606,"name":"Impactify ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://impactify.io/privacy-policy/"},{"id":608,"name":"News and Media Holding, a.s.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.newsandmedia.sk/gdpr/"},{"id":602,"name":"Online Solution Int Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://adsafety.net/privacy.html"},{"id":591,"name":"Consumable, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://consumable.com/privacy-policy.html"},{"id":614,"name":"Market Resource Partners LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.mrpfd.com/privacy-policy/"},{"id":615,"name":"Adsolutions BV","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adsolutions.com/privacy-policy/"},{"id":607,"name":"ucfunnel Co., Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.ucfunnel.com/privacy-policy"},{"id":609,"name":"Predicio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.predic.io/privacy"},{"id":617,"name":"Onfocus (Adagio)","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adagio.io/privacy"},{"id":620,"name":"Blue","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.getblue.io/privacy/"},{"id":610,"name":"Azerion Holding B.V.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://azerion.com/business/privacy.html"},{"id":621,"name":"Seznam.cz, a.s.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://www.seznam.cz/ochranaudaju"},{"id":624,"name":"Norstat AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.norstatpanel.com/en/data-protection"},{"id":623,"name":"Adprime Media Inc. ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adprimehealth.com/privacy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":95,"name":"Lotame Solutions, inc","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[2],"policyUrl":"https://www.lotame.com/about-lotame/privacy/lotame-corporate-websites-privacy-policy/"},{"id":618,"name":"BEINTOO SPA","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.beintoo.com/privacy-cookie-policy/"},{"id":619,"name":"Capitaldata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.capitaldata.fr/privacy"},{"id":625,"name":"BILENDI SA","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.maximiles.com/privacy-policy"},{"id":628,"name":": Tappx","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.tappx.com/en/privacy-policy/"},{"id":626,"name":"Hivestack Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://hivestack.com/privacy-policy"},{"id":631,"name":"Relay42 Netherlands B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://relay42.com/privacy"},{"id":627,"name":"D-Edge","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.d-edge.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":644,"name":"Gamoshi LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.gamoshi.com/privacy-policy"},{"id":639,"name":"Smile Wanted Group","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.smilewanted.com/privacy.php"},{"id":635,"name":"WebMediaRM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webmediarm.com/vie_privee_et_opposition_en.php"},{"id":579,"name":"Ve Global","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.ve.com/privacy-policy"},{"id":645,"name":"Noster Finance S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.finect.com/terminos-legales/politica-de-cookies"},{"id":653,"name":"Smartme Analytics","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"http://smartmeapp.com/info/smartme/aviso_legal.php","deletedDate":"2020-07-03T00:00:00Z"},{"id":613,"name":"Adserve.zone / Artworx AS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adserve.zone/adserveprivacypolicy.html"},{"id":573,"name":"Dailymotion SA","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2],"policyUrl":"https://www.dailymotion.com/legal/privacy"},{"id":652,"name":"Skaze","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.skaze.fr/rgpd/"},{"id":646,"name":"Notify","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"https://notify-group.com/en/mentions-legales/"},{"id":648,"name":"TrueData Solutions, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.truedata.co/privacy-policy/"},{"id":647,"name":"Axel Springer Teaser Ad GmbH","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://www.adup-tech.com/privacy"},{"id":654,"name":"GRAPHINIUM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.graphinium.com/privacy/"},{"id":659,"name":"Research and Analysis of Media in Sweden AB","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www2.rampanel.com/privacy-policy/"},{"id":656,"name":"Think Clever Media","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.contentignite.com/privacy-policy/"},{"id":504,"name":"Alive & Kicking Global Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mcsaatchiplc.com/legal/privacy-cookies","deletedDate":"2020-07-27T00:00:00Z"},{"id":657,"name":"GP One GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.gsi-one.org/de/privacy-policy.html"},{"id":655,"name":"Sportradar AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sportradar.com/about-us/privacy/"},{"id":662,"name":"SoundCast","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://soundcast.fm/en/data-privacy"},{"id":665,"name":"Digital East GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.digitaleast.mobi/en/legal/privacy-policy/"},{"id":650,"name":"Telefonica Investigaci\u00f3n y Desarrollo S.A.U","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.cognitivemarketing.tid.es/"},{"id":666,"name":"BeOp","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://beop.io/privacy"},{"id":663,"name":"Mobsuccess","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.mobsuccess.com/en/privacy"},{"id":658,"name":"BLIINK SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://bliink.io/privacy-policy"},{"id":667,"name":"Liftoff Mobile, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://liftoff.io/privacy-policy/"},{"id":668,"name":"WhatRocks Inc. ","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.whatrocks.co/en/privacy-policy "},{"id":670,"name":"Timehop, Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.timehop.com/privacy"},{"id":674,"name":"Duration Media, LLC.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.durationmedia.net/privacy-policy"},{"id":675,"name":"Instreamatic inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://instreamatic.com/privacy-policy/"},{"id":676,"name":"BusinessClick","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.businessclick.com/documents/RegulaminProgramuBusinessClick-2019.pdf"},{"id":677,"name":"Intercept Interactive Inc. dba Undertone","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.undertone.com/privacy/"},{"id":660,"name":"Schibsted Norge AS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://static.vg.no/privacy/","deletedDate":"2019-09-16T00:00:00Z"},{"id":673,"name":"TTNET AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.programattik.com/en/privacy-policy.aspx"},{"id":664,"name":"adMarketplace, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.admarketplace.com/privacy-policy/"},{"id":671,"name":"Mediaforce LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://casino.mindthebet.co.uk/themes/mindthebetv2-casino/privacy.php"},{"id":561,"name":"AuDigent","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://audigent.com/platform-privacy-policy"},{"id":682,"name":"Radio Net Media Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.adtonos.com/service-privacy-policy/"},{"id":684,"name":"Blue Billywig BV","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.bluebillywig.com/privacy-statement/"},{"id":686,"name":"The MediaGrid Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.themediagrid.com/privacy-policy/"},{"id":685,"name":"Arkeero","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://arkeero.com/privacy-2/"},{"id":687,"name":"MISSENA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://missena.com/confidentialite/"},{"id":690,"name":"Go.pl sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://go.pl/polityka-prywatnosci/"},{"id":691,"name":"Lifesight Pte. Ltd.","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.lifesight.io/privacy-policy/"},{"id":697,"name":"ADWAYS SAS","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.adways.com/confidentialite/?lang=en"},{"id":681,"name":"MyTraffic","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mytraffic.io/en/privacy"},{"id":649,"name":"adality GmbH","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[1],"policyUrl":"https://adality.de/en/privacy/"},{"id":712,"name":"Inspired Mobile Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://byinspired.com/privacypolicy.pdf"},{"id":688,"name":"Effinity","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.effiliation.com/politique-de-confidentialite/"},{"id":702,"name":"Kwanko","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.kwanko.com/fr/rgpd/"},{"id":715,"name":"BidBerry SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.bidberrymedia.com/privacy-policy/"},{"id":713,"name":"Dataseat Ltd","purposeIds":[2,5],"legIntPurposeIds":[1,3,4],"featureIds":[],"policyUrl":"https://dataseat.com/privacy-policy"},{"id":716,"name":"OnAudience Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.onaudience.com/internet-advertising-privacy-policy"},{"id":708,"name":"Dugout Limited ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://dugout.com/privacy-policy"},{"id":717,"name":"Audience Network","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.en.audiencenetwork.pl/internet-advertising-privacy-policy"},{"id":718,"name":"AppConsent Xchange","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://appconsent.io/en/privacy-policy"},{"id":720,"name":"AAX LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://aax.media/privacy/"},{"id":678,"name":"Axonix LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://axonix.com/privacy-cookie-policy/"},{"id":719,"name":"Online Advertising Network Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.oan.pl/en/privacy-policy"},{"id":707,"name":"Dentsu Aegis Network Italia SpA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.dentsuaegisnetwork.com/it/it/policies/info-cookie"},{"id":721,"name":"Beaconspark Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1],"policyUrl":"https://www.engageya.com/privacy"},{"id":724,"name":"Between Exchange","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"https://en.betweenx.com/pdata.pdf"},{"id":728,"name":"Appier PTE Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.appier.com/privacy-policy/"},{"id":729,"name":"Cavai AS & UK ","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://cav.ai/privacy-policy/"},{"id":723,"name":"Adzymic Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.adzymic.co/privacy"},{"id":737,"name":"Monet Engine Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appmonet.com/privacy-policy/"},{"id":740,"name":"6Sense Insights, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://6sense.com/privacy-policy/"},{"id":744,"name":"Vidazoo Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[2],"policyUrl":"https://vidazoo.gitbook.io/vidazoo-legal/privacy-policy"},{"id":731,"name":"GeistM Technologies LTD","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.geistm.com/privacy"},{"id":741,"name":"Brand Advance Limited","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.wearebrandadvance.com/website-privacy-policy"},{"id":734,"name":"Cint AB","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.cint.com/participant-privacy-notice"},{"id":709,"name":"NC Audience Exchange, LLC (NewsIQ)","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.ncaudienceexchange.com/privacy/"},{"id":739,"name":"Blingby LLC","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://blingby.com/privacy"},{"id":732,"name":"Performax.cz, s.r.o.","purposeIds":[2,4,5],"legIntPurposeIds":[1,3],"featureIds":[2,3],"policyUrl":"https://reg.tiscali.cz/privacy-policy"},{"id":736,"name":"BidMachine Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://explorestack.com/privacy-policy/"},{"id":738,"name":"adbility media GmbH","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adbility-media.com/datenschutzerklaerung/"},{"id":742,"name":"Audiencerate LTD","purposeIds":[],"legIntPurposeIds":[1,2,5],"featureIds":[],"policyUrl":"https://www.audiencerate.com/privacy/"},{"id":743,"name":"MOVIads Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://moviads.pl/polityka-prywatnosci/"},{"id":746,"name":"Adxperience SAS","purposeIds":[2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://adxperience.com/privacy-policy/"},{"id":747,"name":"Kairion GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://kairion.de/datenschutzbestimmungen/"},{"id":748,"name":"AUDIOMOB LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.audiomob.io/privacy"},{"id":749,"name":"Good-Loop Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://doc.good-loop.com/policy/privacy-policy.html"},{"id":754,"name":"DistroScale, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.distroscale.com/privacy-policy/"},{"id":756,"name":"Fandom, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"https://www.fandom.com/privacy-policy"},{"id":758,"name":"GfK Netherlands B.V.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://gfkpanel.nl/privacy"},{"id":759,"name":"RevJet","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.revjet.com/privacy"},{"id":760,"name":"VEXPRO TECHNOLOGIES LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://onedash.com/privacy-policy.html"},{"id":761,"name":"Digiseg ApS","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://digiseg.io/privacy-center/"},{"id":763,"name":"Delidatax SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.delidatax.net/privacy.htm"},{"id":764,"name":"Lucidity","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://golucidity.com/privacy-policy/"},{"id":765,"name":"Grabit Interactive Media Inc dba KERV Interctive","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://kervit.com/privacy-policy/"},{"id":766,"name":"ADCELL | Firstlead GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.adcell.de/agb#sector_6"},{"id":768,"name":"Global Media & Entertainment Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://global.com/privacy-policy/"},{"id":770,"name":"MARKETPERF CORP","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.marketperf.com/assets/images/app/marketperf/pdf/privacy-policy.pdf"},{"id":773,"name":"360e-com Sp. z o.o.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.clickonometrics.com/optout/"},{"id":775,"name":"SelectMedia International LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selectmedia.asia/terms-and-privacy/"},{"id":778,"name":"Discover-Tech ltd","purposeIds":[2,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://discover-tech.io/dsp-privacy-policy/"},{"id":779,"name":"Adtarget Medya A.S.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtarget.com.tr/adtarget-privacy-policy-2020.pdf"},{"id":780,"name":"Aniview LTD","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.aniview.com/privacy-policy/"},{"id":781,"name":"FeedAd GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://feedad.com/privacy/"},{"id":784,"name":"Nubo LTD","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.recod3.com/privacypolicy.php"},{"id":786,"name":"TargetVideo GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.target-video.com/datenschutz/"},{"id":798,"name":"Adverticum cPlc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://adverticum.net/english/privacy-and-data-processing-information/"},{"id":803,"name":"Click Tech Limited","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[1],"policyUrl":"https://en.yeahmobi.com/html/privacypolicy/"},{"id":808,"name":"Pure Local Media GmbH","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://purelocalmedia.de/?page_id=593"}]}
\ No newline at end of file
diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go
index 7f92f2521cd..f682ff932f4 100644
--- a/stored_requests/config/config.go
+++ b/stored_requests/config/config.go
@@ -166,6 +166,9 @@ func newFetcher(cfg *config.StoredRequests, client *http.Client, db *sql.DB) (fe
if cfg.Postgres.FetcherQueries.QueryTemplate != "" {
glog.Infof("Loading Stored %s data via Postgres.\nQuery: %s", cfg.DataType(), cfg.Postgres.FetcherQueries.QueryTemplate)
idList = append(idList, db_fetcher.NewFetcher(db, cfg.Postgres.FetcherQueries.MakeQuery))
+ } else if cfg.Postgres.CacheInitialization.Query != "" && cfg.Postgres.PollUpdates.Query != "" {
+ //in this case data will be loaded to cache via poll for updates event
+ idList = append(idList, empty_fetcher.EmptyFetcher{})
}
if cfg.HTTP.Endpoint != "" {
glog.Infof("Loading Stored %s data via HTTP. endpoint=%s", cfg.DataType(), cfg.HTTP.Endpoint)
diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go
index 6c8cd612299..4a8d10a9382 100644
--- a/stored_requests/config/config_test.go
+++ b/stored_requests/config/config_test.go
@@ -2,6 +2,7 @@ package config
import (
"context"
+ "database/sql"
"encoding/json"
"errors"
"net/http"
@@ -41,12 +42,79 @@ func isMemoryCacheType(cache stored_requests.CacheJSON) bool {
}
func TestNewEmptyFetcher(t *testing.T) {
- fetcher := newFetcher(&config.StoredRequests{}, nil, nil)
- if fetcher == nil {
- t.Errorf("The fetcher should be non-nil, even with an empty config.")
+
+ type testCase struct {
+ config *config.StoredRequests
+ emptyFetcher bool
+ description string
}
- if _, ok := fetcher.(empty_fetcher.EmptyFetcher); !ok {
- t.Errorf("If the config is empty, and EmptyFetcher should be returned")
+ testCases := []testCase{
+ {
+ config: &config.StoredRequests{},
+ emptyFetcher: true,
+ description: "If the config is empty, an EmptyFetcher should be returned",
+ },
+ {
+ config: &config.StoredRequests{
+ Postgres: config.PostgresConfig{
+ CacheInitialization: config.PostgresCacheInitializer{
+ Query: "test query",
+ },
+ PollUpdates: config.PostgresUpdatePolling{
+ Query: "test poll query",
+ },
+ FetcherQueries: config.PostgresFetcherQueries{
+ QueryTemplate: "",
+ },
+ },
+ },
+ emptyFetcher: true,
+ description: "If Postgres fetcher query is not defined, but Postgres Cache init query and Postgres update polling query are defined EmptyFetcher should be returned",
+ },
+ {
+ config: &config.StoredRequests{
+ Postgres: config.PostgresConfig{
+ CacheInitialization: config.PostgresCacheInitializer{
+ Query: "",
+ },
+ PollUpdates: config.PostgresUpdatePolling{
+ Query: "",
+ },
+ FetcherQueries: config.PostgresFetcherQueries{
+ QueryTemplate: "test fetcher query",
+ },
+ },
+ },
+ emptyFetcher: false,
+ description: "If Postgres fetcher query is defined, but Postgres Cache init query and Postgres update polling query are not defined not EmptyFetcher (DBFetcher) should be returned",
+ },
+ {
+ config: &config.StoredRequests{
+ Postgres: config.PostgresConfig{
+ CacheInitialization: config.PostgresCacheInitializer{
+ Query: "test cache query",
+ },
+ PollUpdates: config.PostgresUpdatePolling{
+ Query: "test poll query",
+ },
+ FetcherQueries: config.PostgresFetcherQueries{
+ QueryTemplate: "test fetcher query",
+ },
+ },
+ },
+ emptyFetcher: false,
+ description: "If Postgres fetcher query is defined and Postgres Cache init query and Postgres update polling query are defined not EmptyFetcher (DBFetcher) should be returned",
+ },
+ }
+
+ for _, test := range testCases {
+ fetcher := newFetcher(test.config, nil, &sql.DB{})
+ assert.NotNil(t, fetcher, "The fetcher should be non-nil.")
+ if test.emptyFetcher {
+ assert.Equal(t, empty_fetcher.EmptyFetcher{}, fetcher, "Empty fetcher should be returned")
+ } else {
+ assert.NotEqual(t, empty_fetcher.EmptyFetcher{}, fetcher)
+ }
}
}
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index 50362ad04ec..8275869f5b2 100644
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -1,12 +1,15 @@
package usersyncers
import (
+ "github.com/prebid/prebid-server/adapters/operaads"
"strings"
"text/template"
"github.com/golang/glog"
ttx "github.com/prebid/prebid-server/adapters/33across"
"github.com/prebid/prebid-server/adapters/acuityads"
+ "github.com/prebid/prebid-server/adapters/adagio"
+ "github.com/prebid/prebid-server/adapters/adf"
"github.com/prebid/prebid-server/adapters/adform"
"github.com/prebid/prebid-server/adapters/adkernel"
"github.com/prebid/prebid-server/adapters/adkernelAdn"
@@ -27,6 +30,8 @@ import (
"github.com/prebid/prebid-server/adapters/beachfront"
"github.com/prebid/prebid-server/adapters/beintoo"
"github.com/prebid/prebid-server/adapters/between"
+ "github.com/prebid/prebid-server/adapters/bidmyadz"
+ "github.com/prebid/prebid-server/adapters/bmtm"
"github.com/prebid/prebid-server/adapters/brightroll"
"github.com/prebid/prebid-server/adapters/colossus"
"github.com/prebid/prebid-server/adapters/connectad"
@@ -37,6 +42,7 @@ import (
"github.com/prebid/prebid-server/adapters/datablocks"
"github.com/prebid/prebid-server/adapters/deepintent"
"github.com/prebid/prebid-server/adapters/dmx"
+ "github.com/prebid/prebid-server/adapters/e_volution"
"github.com/prebid/prebid-server/adapters/emx_digital"
"github.com/prebid/prebid-server/adapters/engagebdr"
"github.com/prebid/prebid-server/adapters/eplanning"
@@ -45,11 +51,11 @@ import (
"github.com/prebid/prebid-server/adapters/grid"
"github.com/prebid/prebid-server/adapters/gumgum"
"github.com/prebid/prebid-server/adapters/improvedigital"
+ "github.com/prebid/prebid-server/adapters/inmobi"
"github.com/prebid/prebid-server/adapters/invibes"
"github.com/prebid/prebid-server/adapters/ix"
"github.com/prebid/prebid-server/adapters/jixie"
"github.com/prebid/prebid-server/adapters/krushmedia"
- "github.com/prebid/prebid-server/adapters/lifestreet"
"github.com/prebid/prebid-server/adapters/lockerdome"
"github.com/prebid/prebid-server/adapters/logicad"
"github.com/prebid/prebid-server/adapters/lunamedia"
@@ -67,10 +73,12 @@ import (
"github.com/prebid/prebid-server/adapters/rhythmone"
"github.com/prebid/prebid-server/adapters/rtbhouse"
"github.com/prebid/prebid-server/adapters/rubicon"
+ "github.com/prebid/prebid-server/adapters/sa_lunamedia"
"github.com/prebid/prebid-server/adapters/sharethrough"
"github.com/prebid/prebid-server/adapters/smartadserver"
"github.com/prebid/prebid-server/adapters/smartrtb"
"github.com/prebid/prebid-server/adapters/smartyads"
+ "github.com/prebid/prebid-server/adapters/smilewanted"
"github.com/prebid/prebid-server/adapters/somoaudience"
"github.com/prebid/prebid-server/adapters/sonobi"
"github.com/prebid/prebid-server/adapters/sovrn"
@@ -84,6 +92,7 @@ import (
"github.com/prebid/prebid-server/adapters/unruly"
"github.com/prebid/prebid-server/adapters/valueimpression"
"github.com/prebid/prebid-server/adapters/verizonmedia"
+ "github.com/prebid/prebid-server/adapters/viewdeos"
"github.com/prebid/prebid-server/adapters/visx"
"github.com/prebid/prebid-server/adapters/vrtcal"
"github.com/prebid/prebid-server/adapters/yieldlab"
@@ -103,6 +112,8 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
insertIntoMap(cfg, syncers, openrtb_ext.Bidder33Across, ttx.New33AcrossSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderAcuityAds, acuityads.NewAcuityAdsSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderAdagio, adagio.NewAdagioSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderAdf, adf.NewAdfSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderAdform, adform.NewAdformSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernel, adkernel.NewAdkernelSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernelAdn, adkernelAdn.NewAdkernelAdnSyncer)
@@ -121,7 +132,9 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
insertIntoMap(cfg, syncers, openrtb_ext.BidderAvocet, avocet.NewAvocetSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderBeachfront, beachfront.NewBeachfrontSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderBeintoo, beintoo.NewBeintooSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderBmtm, bmtm.NewBmtmSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderBidmyadz, bidmyadz.NewBidmyadzSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderColossus, colossus.NewColossusSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderConnectAd, connectad.NewConnectAdSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer)
@@ -132,6 +145,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
insertIntoMap(cfg, syncers, openrtb_ext.BidderDeepintent, deepintent.NewDeepintentSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderDmx, dmx.NewDmxSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderEVolution, evolution.NewEvolutionSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderEngageBDR, engagebdr.NewEngageBDRSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderEPlanning, eplanning.NewEPlanningSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderAudienceNetwork, audienceNetwork.NewFacebookSyncer)
@@ -140,14 +154,15 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
insertIntoMap(cfg, syncers, openrtb_ext.BidderGrid, grid.NewGridSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderGumGum, gumgum.NewGumGumSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderImprovedigital, improvedigital.NewImprovedigitalSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderInMobi, inmobi.NewInmobiSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderInvibes, invibes.NewInvibesSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderJixie, jixie.NewJixieSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderKrushmedia, krushmedia.NewKrushmediaSyncer)
- insertIntoMap(cfg, syncers, openrtb_ext.BidderLifestreet, lifestreet.NewLifestreetSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderLogicad, logicad.NewLogicadSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderSaLunaMedia, salunamedia.NewSaLunamediaSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderMediafuse, mediafuse.NewMediafuseSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer)
@@ -157,6 +172,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
insertIntoMap(cfg, syncers, openrtb_ext.BidderOneTag, onetag.NewSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderOutbrain, outbrain.NewOutbrainSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderOpenx, openx.NewOpenxSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderOperaads, operaads.NewOperaadsSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderRhythmone, rhythmone.NewRhythmoneSyncer)
@@ -169,6 +185,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartAdserver, smartadserver.NewSmartadserverSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartyAds, smartyads.NewSmartyAdsSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderSmileWanted, smilewanted.NewSmileWantedSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderTappx, tappx.NewTappxSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer)
@@ -179,6 +196,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
insertIntoMap(cfg, syncers, openrtb_ext.BidderUnruly, unruly.NewUnrulySyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderValueImpression, valueimpression.NewValueImpressionSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderVerizonMedia, verizonmedia.NewVerizonMediaSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderViewdeos, viewdeos.NewViewdeosSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderVisx, visx.NewVisxSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderVrtcal, vrtcal.NewVrtcalSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldlab, yieldlab.NewYieldlabSyncer)
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
old mode 100755
new mode 100644
index 10a95fb4b67..d284ddec035
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -16,6 +16,8 @@ func TestNewSyncerMap(t *testing.T) {
Adapters: map[string]config.Adapter{
string(openrtb_ext.Bidder33Across): syncConfig,
string(openrtb_ext.BidderAcuityAds): syncConfig,
+ string(openrtb_ext.BidderAdagio): syncConfig,
+ string(openrtb_ext.BidderAdf): syncConfig,
string(openrtb_ext.BidderAdform): syncConfig,
string(openrtb_ext.BidderAdkernel): syncConfig,
string(openrtb_ext.BidderAdkernelAdn): syncConfig,
@@ -36,6 +38,8 @@ func TestNewSyncerMap(t *testing.T) {
string(openrtb_ext.BidderBeachfront): syncConfig,
string(openrtb_ext.BidderBeintoo): syncConfig,
string(openrtb_ext.BidderBetween): syncConfig,
+ string(openrtb_ext.BidderBidmyadz): syncConfig,
+ string(openrtb_ext.BidderBmtm): syncConfig,
string(openrtb_ext.BidderBrightroll): syncConfig,
string(openrtb_ext.BidderColossus): syncConfig,
string(openrtb_ext.BidderConnectAd): syncConfig,
@@ -49,19 +53,21 @@ func TestNewSyncerMap(t *testing.T) {
string(openrtb_ext.BidderEmxDigital): syncConfig,
string(openrtb_ext.BidderEngageBDR): syncConfig,
string(openrtb_ext.BidderEPlanning): syncConfig,
+ string(openrtb_ext.BidderEVolution): syncConfig,
string(openrtb_ext.BidderGamma): syncConfig,
string(openrtb_ext.BidderGamoshi): syncConfig,
string(openrtb_ext.BidderGrid): syncConfig,
string(openrtb_ext.BidderGumGum): syncConfig,
string(openrtb_ext.BidderImprovedigital): syncConfig,
+ string(openrtb_ext.BidderInMobi): syncConfig,
string(openrtb_ext.BidderInvibes): syncConfig,
string(openrtb_ext.BidderIx): syncConfig,
string(openrtb_ext.BidderJixie): syncConfig,
string(openrtb_ext.BidderKrushmedia): syncConfig,
- string(openrtb_ext.BidderLifestreet): syncConfig,
string(openrtb_ext.BidderLockerDome): syncConfig,
string(openrtb_ext.BidderLogicad): syncConfig,
string(openrtb_ext.BidderLunaMedia): syncConfig,
+ string(openrtb_ext.BidderSaLunaMedia): syncConfig,
string(openrtb_ext.BidderMarsmedia): syncConfig,
string(openrtb_ext.BidderMediafuse): syncConfig,
string(openrtb_ext.BidderMgid): syncConfig,
@@ -70,6 +76,7 @@ func TestNewSyncerMap(t *testing.T) {
string(openrtb_ext.BidderNoBid): syncConfig,
string(openrtb_ext.BidderOneTag): syncConfig,
string(openrtb_ext.BidderOpenx): syncConfig,
+ string(openrtb_ext.BidderOperaads): syncConfig,
string(openrtb_ext.BidderOutbrain): syncConfig,
string(openrtb_ext.BidderPubmatic): syncConfig,
string(openrtb_ext.BidderPulsepoint): syncConfig,
@@ -80,6 +87,7 @@ func TestNewSyncerMap(t *testing.T) {
string(openrtb_ext.BidderSmartAdserver): syncConfig,
string(openrtb_ext.BidderSmartRTB): syncConfig,
string(openrtb_ext.BidderSmartyAds): syncConfig,
+ string(openrtb_ext.BidderSmileWanted): syncConfig,
string(openrtb_ext.BidderSomoaudience): syncConfig,
string(openrtb_ext.BidderSonobi): syncConfig,
string(openrtb_ext.BidderSovrn): syncConfig,
@@ -93,6 +101,7 @@ func TestNewSyncerMap(t *testing.T) {
string(openrtb_ext.BidderUnruly): syncConfig,
string(openrtb_ext.BidderValueImpression): syncConfig,
string(openrtb_ext.BidderVerizonMedia): syncConfig,
+ string(openrtb_ext.BidderViewdeos): syncConfig,
string(openrtb_ext.BidderVisx): syncConfig,
string(openrtb_ext.BidderVrtcal): syncConfig,
string(openrtb_ext.BidderYieldlab): syncConfig,
@@ -103,30 +112,35 @@ func TestNewSyncerMap(t *testing.T) {
}
adaptersWithoutSyncers := map[openrtb_ext.BidderName]bool{
- openrtb_ext.BidderAdgeneration: true,
- openrtb_ext.BidderAdhese: true,
- openrtb_ext.BidderAdoppler: true,
- openrtb_ext.BidderAdot: true,
- openrtb_ext.BidderAdprime: true,
- openrtb_ext.BidderApplogy: true,
- openrtb_ext.BidderBidmachine: true,
- openrtb_ext.BidderEpom: true,
- openrtb_ext.BidderDecenterAds: true,
- openrtb_ext.BidderInMobi: true,
- openrtb_ext.BidderKidoz: true,
- openrtb_ext.BidderKubient: true,
- openrtb_ext.BidderMobfoxpb: true,
- openrtb_ext.BidderMobileFuse: true,
- openrtb_ext.BidderOrbidder: true,
- openrtb_ext.BidderPangle: true,
- openrtb_ext.BidderPubnative: true,
- openrtb_ext.BidderRevcontent: true,
- openrtb_ext.BidderSilverMob: true,
- openrtb_ext.BidderSmaato: true,
- openrtb_ext.BidderSpotX: true,
- openrtb_ext.BidderVASTBidder: true,
- openrtb_ext.BidderUnicorn: true,
- openrtb_ext.BidderYeahmobi: true,
+ openrtb_ext.BidderAdgeneration: true,
+ openrtb_ext.BidderAdhese: true,
+ openrtb_ext.BidderAdoppler: true,
+ openrtb_ext.BidderAdot: true,
+ openrtb_ext.BidderAdprime: true,
+ openrtb_ext.BidderAlgorix: true,
+ openrtb_ext.BidderApplogy: true,
+ openrtb_ext.BidderAxonix: true,
+ openrtb_ext.BidderBidmachine: true,
+ openrtb_ext.BidderBidsCube: true,
+ openrtb_ext.BidderEpom: true,
+ openrtb_ext.BidderDecenterAds: true,
+ openrtb_ext.BidderInteractiveoffers: true,
+ openrtb_ext.BidderKayzen: true,
+ openrtb_ext.BidderKidoz: true,
+ openrtb_ext.BidderKubient: true,
+ openrtb_ext.BidderMadvertise: true,
+ openrtb_ext.BidderMobfoxpb: true,
+ openrtb_ext.BidderMobileFuse: true,
+ openrtb_ext.BidderOrbidder: true,
+ openrtb_ext.BidderPangle: true,
+ openrtb_ext.BidderPubnative: true,
+ openrtb_ext.BidderRevcontent: true,
+ openrtb_ext.BidderSilverMob: true,
+ openrtb_ext.BidderSmaato: true,
+ openrtb_ext.BidderSpotX: true,
+ openrtb_ext.BidderUnicorn: true,
+ openrtb_ext.BidderVASTBidder: true,
+ openrtb_ext.BidderYeahmobi: true,
}
for bidder, config := range cfg.Adapters {
diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go
new file mode 100644
index 00000000000..963477de7aa
--- /dev/null
+++ b/util/jsonutil/jsonutil.go
@@ -0,0 +1,57 @@
+package jsonutil
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+)
+
+var comma = []byte(",")[0]
+
+func DropElement(extension []byte, elementName string) ([]byte, error) {
+ buf := bytes.NewBuffer(extension)
+ dec := json.NewDecoder(buf)
+ var startIndex int64
+ var i interface{}
+ for {
+ token, err := dec.Token()
+ if err == io.EOF {
+ // io.EOF is a successful end
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ if token == elementName {
+ err := dec.Decode(&i)
+ if err != nil {
+ return nil, err
+ }
+ endIndex := dec.InputOffset()
+
+ if dec.More() {
+ //if there were other elements before
+ if extension[startIndex] == comma {
+ startIndex++
+ }
+
+ for {
+ //structure has more elements, need to find index of comma
+ if extension[endIndex] == comma {
+ endIndex++
+ break
+ }
+ endIndex++
+ }
+ }
+
+ extension = append(extension[:startIndex], extension[endIndex:]...)
+ break
+ } else {
+ startIndex = dec.InputOffset()
+ }
+
+ }
+ return extension, nil
+}
diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go
new file mode 100644
index 00000000000..0b6ec34c4ed
--- /dev/null
+++ b/util/jsonutil/jsonutil_test.go
@@ -0,0 +1,122 @@
+package jsonutil
+
+import (
+ "github.com/stretchr/testify/assert"
+ "strings"
+ "testing"
+)
+
+func TestDropElement(t *testing.T) {
+
+ tests := []struct {
+ description string
+ input []byte
+ elementToRemove string
+ output []byte
+ errorExpected bool
+ errorContains string
+ }{
+ {
+ description: "Drop Single Element After Another Element",
+ input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`),
+ elementToRemove: "consented_providers",
+ output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`),
+ errorExpected: false,
+ errorContains: "",
+ },
+ {
+ description: "Drop Single Element Before Another Element",
+ input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`),
+ elementToRemove: "consented_providers",
+ output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`),
+ errorExpected: false,
+ errorContains: "",
+ },
+ {
+ description: "Drop Single Element",
+ input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`),
+ elementToRemove: "consented_providers",
+ output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`),
+ errorExpected: false,
+ errorContains: "",
+ },
+ {
+ description: "Drop Single Element string",
+ input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`),
+ elementToRemove: "consented_providers",
+ output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`),
+ errorExpected: false,
+ errorContains: "",
+ },
+ {
+ description: "Drop Parent Element Between Two Elements",
+ input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`),
+ elementToRemove: "consented_providers_settings",
+ output: []byte(`{"consent": "TESTCONSENT","test": 123}`),
+ errorExpected: false,
+ errorContains: "",
+ },
+ {
+ description: "Drop Parent Element Before Element",
+ input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`),
+ elementToRemove: "consented_providers_settings",
+ output: []byte(`{"test": 123}`),
+ errorExpected: false,
+ errorContains: "",
+ },
+ {
+ description: "Drop Parent Element After Element",
+ input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
+ elementToRemove: "consented_providers_settings",
+ output: []byte(`{"consent": "TESTCONSENT"}`),
+ errorExpected: false,
+ errorContains: "",
+ },
+ {
+ description: "Drop Parent Element Only",
+ input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
+ elementToRemove: "consented_providers_settings",
+ output: []byte(`{}`),
+ errorExpected: false,
+ errorContains: "",
+ },
+ {
+ description: "Drop Element That Doesn't Exist",
+ input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
+ elementToRemove: "test2",
+ output: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
+ errorExpected: false,
+ errorContains: "",
+ },
+ //Errors
+ {
+ description: "Error Decode",
+ input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`),
+ elementToRemove: "consented_providers",
+ output: []byte(``),
+ errorExpected: true,
+ errorContains: "looking for beginning of value",
+ },
+ {
+ description: "Error Malformed",
+ input: []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`),
+ elementToRemove: "consented_providers",
+ output: []byte(``),
+ errorExpected: true,
+ errorContains: "invalid character",
+ },
+ }
+
+ for _, tt := range tests {
+ res, err := DropElement(tt.input, tt.elementToRemove)
+
+ if tt.errorExpected {
+ assert.Error(t, err, "Error should not be nil")
+ assert.True(t, strings.Contains(err.Error(), tt.errorContains))
+ } else {
+ assert.NoError(t, err, "Error should be nil")
+ assert.Equal(t, tt.output, res, "Result is incorrect")
+ }
+
+ }
+}