From fc9f9546ac2a0f782c892d48a558f05ce2c0a984 Mon Sep 17 00:00:00 2001 From: Jim Naumann Date: Tue, 8 Jun 2021 23:43:41 -0400 Subject: [PATCH 001/140] Beachfront: Fix for bid floor issue#1787 (#1878) Co-authored-by: jim naumann --- adapters/beachfront/beachfront.go | 33 +- .../supplemental/banner-bad-request-400.json | 2 +- .../supplemental/bidfloor-below-min.json | 102 ----- ...-four-variations-on-nothing-adm-video.json | 428 ++++++++++++++++++ .../bidfloor-test-ext-wins-adm-video.json | 124 +++++ .../bidfloor-test-imp-wins-adm-video.json | 125 +++++ adapters/beachfront/params_test.go | 36 +- static/bidder-params/beachfront.json | 21 +- 8 files changed, 747 insertions(+), 124 deletions(-) delete mode 100644 adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidfloor-four-variations-on-nothing-adm-video.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidfloor-test-ext-wins-adm-video.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidfloor-test-imp-wins-adm-video.json diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index c5f6c4d5b1e..6eba9923e64 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -294,14 +294,12 @@ func getBannerRequest(request *openrtb2.BidRequest) (beachfrontBannerRequest, [] continue } + setBidFloor(&beachfrontExt, &request.Imp[i]) + slot := beachfrontSlot{ Id: appid, Slot: request.Imp[i].ID, - Bidfloor: beachfrontExt.BidFloor, - } - - if beachfrontExt.BidFloor <= minBidFloor { - slot.Bidfloor = 0 + Bidfloor: request.Imp[i].BidFloor, } for j := 0; j < len(request.Imp[i].Banner.Format); j++ { @@ -468,12 +466,7 @@ func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, [ imp.Banner = nil imp.Ext = nil imp.Secure = &secure - - if beachfrontExt.BidFloor <= minBidFloor { - imp.BidFloor = 0 - } else { - imp.BidFloor = beachfrontExt.BidFloor - } + setBidFloor(&beachfrontExt, &imp) if imp.Video.H == 0 && imp.Video.W == 0 { imp.Video.W = defaultVideoWidth @@ -564,6 +557,24 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb2.BidRequest, exter return bidResponse, errs } +func setBidFloor(ext *openrtb_ext.ExtImpBeachfront, imp *openrtb2.Imp) { + var floor float64 + + if imp.BidFloor > 0 { + floor = imp.BidFloor + } else if ext.BidFloor > 0 { + floor = ext.BidFloor + } else { + floor = minBidFloor + } + + if floor <= minBidFloor { + floor = 0 + } + + imp.BidFloor = floor +} + func (a *BeachfrontAdapter) getBidType(externalRequest *adapters.RequestData) openrtb_ext.BidType { t := strings.Split(externalRequest.Uri, "=")[0] if t == a.extraInfo.VideoEndpoint { diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json index 426499586f5..7463e2bf374 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json @@ -34,7 +34,7 @@ { "slot": "", "id": "dudAppId", - "bidfloor": 5.02, + "bidfloor": 0.02, "sizes": [ { "w": 300, diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json deleted file mode 100644 index 70b89e5db1d..00000000000 --- a/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "mockBidRequest": { - "id": "some_test_ad", - "site": { - "page": "https://some.domain.us/some/page.html" - }, - "imp": [ - { - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "bidfloor": 0.002, - "appId": "bannerAppId1" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://qa.beachrtb.com/prebid_display", - "body": { - "slots": [ - { - "slot": "", - "id": "bannerAppId1", - "bidfloor": 0, - "sizes": [ - { - "w": 300, - "h": 250 - } - ] - } - ], - "domain": "some.domain.us", - "page": "https://some.domain.us/some/page.html", - "real204": true, - "referrer": "", - "search": "", - "secure": 1, - "requestId": "some_test_ad", - "isMobile": 0, - "ip": "", - "deviceModel": "", - "deviceOs": "", - "dnt": 0, - "ua": "", - "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.2", - "user": {}, - "schain": { - "complete": 0, - "nodes": null, - "ver": "" - } - } - }, - "mockResponse": { - "status": 200, - "body": [ - { - "crid": "crid_1", - "price": 2.942808, - "w": 300, - "h": 250, - "slot": "div-gpt-ad-1460505748561-0", - "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\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/router/router.go b/router/router.go index 3b51d0730c1..e1c42699225 100644 --- a/router/router.go +++ b/router/router.go @@ -23,7 +23,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" @@ -160,7 +159,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/lifestreet.yaml b/static/bidder-info/lifestreet.yaml deleted file mode 100644 index 34dc4eca2d9..00000000000 --- a/static/bidder-info/lifestreet.yaml +++ /dev/null @@ -1,11 +0,0 @@ -maintainer: - email: "mobile.tech@lifestreet.com" -gvlVendorID: 67 -capabilities: - app: - mediaTypes: - - banner - site: - mediaTypes: - - banner - - video diff --git a/static/bidder-params/lifestreet.json b/static/bidder-params/lifestreet.json deleted file mode 100644 index 2190d761e69..00000000000 --- a/static/bidder-params/lifestreet.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Lifestreet Adapter Params", - "description": "A schema which validates params accepted by the Lifestreet adapter", - "type": "object", - "properties": { - "slot_tag": { - "type": "string", - "description": "A tag which identifies the ad slot" - } - }, - "required": ["slot_tag"] -} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 1c4e809e72b..a3f32320796 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -51,7 +51,6 @@ import ( "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" @@ -149,7 +148,6 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync 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) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 10aaafd5985..39da3955fd9 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -60,7 +60,6 @@ func TestNewSyncerMap(t *testing.T) { 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, From 7267f6e855f9f38eda179ea3c4f1629a22b62766 Mon Sep 17 00:00:00 2001 From: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Date: Thu, 10 Jun 2021 18:53:26 +0300 Subject: [PATCH 008/140] New Adapter: E-Volution (#1868) --- adapters/e_volution/evolution.go | 112 ++++++++++ adapters/e_volution/evolution_test.go | 18 ++ .../exemplary/banner-without-mediatype.json | 168 +++++++++++++++ .../evolutiontest/exemplary/banner.json | 174 +++++++++++++++ .../evolutiontest/exemplary/native.json | 181 ++++++++++++++++ .../evolutiontest/exemplary/video.json | 200 ++++++++++++++++++ .../evolutiontest/params/race/banner.json | 3 + .../evolutiontest/params/race/native.json | 3 + .../evolutiontest/params/race/video.json | 3 + .../supplemental/bad-response.json | 158 ++++++++++++++ .../supplemental/empty-seatbid.json | 149 +++++++++++++ .../supplemental/status-204.json | 126 +++++++++++ .../supplemental/status-400.json | 133 ++++++++++++ .../supplemental/status-503.json | 125 +++++++++++ .../supplemental/unexpected-status.json | 132 ++++++++++++ adapters/e_volution/params_test.go | 49 +++++ adapters/e_volution/usersync.go | 12 ++ adapters/e_volution/usersync_test.go | 33 +++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/e_volution.yaml | 14 ++ static/bidder-params/e_volution.json | 13 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 25 files changed, 1815 insertions(+) create mode 100644 adapters/e_volution/evolution.go create mode 100644 adapters/e_volution/evolution_test.go create mode 100644 adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/banner.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/native.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/video.json create mode 100644 adapters/e_volution/evolutiontest/params/race/banner.json create mode 100644 adapters/e_volution/evolutiontest/params/race/native.json create mode 100644 adapters/e_volution/evolutiontest/params/race/video.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/bad-response.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-204.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-400.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-503.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/unexpected-status.json create mode 100644 adapters/e_volution/params_test.go create mode 100644 adapters/e_volution/usersync.go create mode 100644 adapters/e_volution/usersync_test.go create mode 100644 static/bidder-info/e_volution.yaml create mode 100644 static/bidder-params/e_volution.json diff --git a/adapters/e_volution/evolution.go b/adapters/e_volution/evolution.go new file mode 100644 index 00000000000..26df301cdb7 --- /dev/null +++ b/adapters/e_volution/evolution.go @@ -0,0 +1,112 @@ +package evolution + +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 { + URI string +} + +type bidExt struct { + MediaType openrtb_ext.BidType `json:"mediaType"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + URI: 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.URI, + 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, nil + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", bidderRawResponse.StatusCode), + }} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad response, %s", err), + }} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Empty seatbid"), + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + sb := bidResp.SeatBid[0] + for i := range sb.Bid { + var bidType openrtb_ext.BidType + var bidExt bidExt + if err := json.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil { + bidType = openrtb_ext.BidTypeBanner + } else { + bidType = bidExt.MediaType + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + } + return bidResponse, nil +} diff --git a/adapters/e_volution/evolution_test.go b/adapters/e_volution/evolution_test.go new file mode 100644 index 00000000000..1d2ee7ef9a6 --- /dev/null +++ b/adapters/e_volution/evolution_test.go @@ -0,0 +1,18 @@ +package evolution + +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.BidderEVolution, config.Adapter{ + Endpoint: "http://service.e-volution.ai/pbserver"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "evolutiontest", bidder) +} diff --git a/adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json b/adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json new file mode 100644 index 00000000000..251fe8c6f87 --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json @@ -0,0 +1,168 @@ +{ + "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 + }], + "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" + }, + "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/config/config.go b/config/config.go index 2d8fb129e42..a259a9aa4e1 100644 --- a/config/config.go +++ b/config/config.go @@ -604,6 +604,7 @@ func (cfg *Configuration) setDerivedDefaults() { // openrtb_ext.BidderDMX doesn't have a good default. 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. @@ -862,6 +863,7 @@ 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 4965f7f5019..a773d268604 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -46,6 +46,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" @@ -169,6 +170,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.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, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 082498d2893..ea6c809fd9b 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -121,6 +121,7 @@ const ( BidderEngageBDR BidderName = "engagebdr" BidderEPlanning BidderName = "eplanning" BidderEpom BidderName = "epom" + BidderEVolution BidderName = "e_volution" BidderGamma BidderName = "gamma" BidderGamoshi BidderName = "gamoshi" BidderGrid BidderName = "grid" @@ -240,6 +241,7 @@ func CoreBidderNames() []BidderName { BidderEngageBDR, BidderEPlanning, BidderEpom, + BidderEVolution, BidderGamma, BidderGamoshi, BidderGrid, 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-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/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index a3f32320796..88752f4d7d7 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -39,6 +39,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" @@ -136,6 +137,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) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 39da3955fd9..2ebd541d015 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -51,6 +51,7 @@ 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, From 08ad3eef8fba678f4b82461184651b46f49b6d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Thu, 10 Jun 2021 18:55:20 +0200 Subject: [PATCH 009/140] [criteo] accept zoneId and networkId alternate case (#1869) --- .../supplemental/multislots-alt-case.json | 232 ++++++++++++++++++ adapters/criteo/params_test.go | 7 + static/bidder-params/criteo.json | 78 +++--- 3 files changed, 288 insertions(+), 29 deletions(-) create mode 100644 adapters/criteo/criteotest/supplemental/multislots-alt-case.json diff --git a/adapters/criteo/criteotest/supplemental/multislots-alt-case.json b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json new file mode 100644 index 00000000000..beb855e3f2b --- /dev/null +++ b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json @@ -0,0 +1,232 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 123456, + "networkId": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 7891011, + "networkId": 78910 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": 121314, + "networkId": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "cpm": 0.2, + "currency": "USD", + "width": 320, + "height": 50, + "creativeid": "creative-123", + "creative": "" + }, + { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "cpm": 0.3, + "currency": "USD", + "width": 300, + "height": 600, + "creativeid": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "price": 0.2, + "crid": "creative-123", + "adm": "", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "price": 0.3, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/params_test.go b/adapters/criteo/params_test.go index 73ace617b2d..9c836769aca 100644 --- a/adapters/criteo/params_test.go +++ b/adapters/criteo/params_test.go @@ -41,9 +41,13 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"zoneid": 123456}`, + `{"zoneId": 123456}`, `{"networkid": 78910}`, + `{"networkId": 78910}`, `{"zoneid": 123456, "networkid": 78910}`, + `{"zoneId": 123456, "networkId": 78910}`, `{"zoneid": 0, "networkid": 0}`, + `{"zoneId": 0, "networkId": 0}`, } var invalidParams = []string{ @@ -55,8 +59,11 @@ var invalidParams = []string{ `[]`, `{}`, `{"zoneid": -123}`, + `{"zoneId": -123}`, `{"networkid": -321}`, + `{"networkId": -321}`, `{"zoneid": -123, "networkid": -321}`, + `{"zoneId": -123, "networkId": -321}`, `{"zoneid": -1}`, `{"networkid": -1}`, `{"zoneid": -1, "networkid": -1}`, 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 From 486926600eeb669f69a1b753e583cca311e873d0 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 10 Jun 2021 10:17:18 -0700 Subject: [PATCH 010/140] Unit test random map order fix (#1887) Co-authored-by: Veronika Solovei --- exchange/exchange_test.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index a03c4786b79..d3bcf082cf2 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2623,26 +2623,42 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) 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") - assert.Len(t, adapterBids[bidderNameApn1].bids, 0) - assert.Len(t, adapterBids[bidderNameApn2].bids, 2) + if firstBidderIndicator { + assert.Len(t, adapterBids[bidderNameApn1].bids, 2) + assert.Len(t, adapterBids[bidderNameApn2].bids, 0) - 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_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.Len(t, rejections, 2, "2 bids should be de-duplicated") - 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") + 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) { From 1993de4cb8db5537d83ede873b50902e24799808 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 10 Jun 2021 13:57:51 -0400 Subject: [PATCH 011/140] Request Provided Currency Rates (#1753) --- currency/aggregate_conversions.go | 41 ++ currency/aggregate_conversions_test.go | 89 ++++ currency/constant_rates.go | 4 +- currency/errors.go | 13 + currency/rates.go | 14 +- endpoints/openrtb2/auction.go | 29 ++ endpoints/openrtb2/auction_test.go | 245 ++++++++++- .../no-account/not-required-no-acct.json | 1 + .../with-account/required-with-acct.json | 1 + .../aliased/multiple-alias.json | 1 + .../sample-requests/aliased/simple.json | 25 +- .../errors/conversion-disabled.json | 46 ++ ...rates-currency-missing-usepbs-default.json | 52 +++ ...m-rates-currency-missing-usepbs-false.json | 53 +++ .../custom-rates-empty-usepbs-false.json | 49 +++ .../custom-rates-invalid-usepbs-false.json | 53 +++ .../valid/conversion-disabled.json | 62 +++ ...om-rate-not-found-usepbsrates-default.json | 70 ++++ ...om-rates-override-usepbsrates-default.json | 69 +++ ...stom-rates-override-usepbsrates-false.json | 70 ++++ ...ustom-rates-override-usepbsrates-true.json | 70 ++++ .../valid/reverse-currency-conversion.json | 66 +++ .../valid/server-rates-usepbsrates-true.json | 70 ++++ .../errors/no-conversion-found.json | 38 ++ .../server-rates/valid/simple-conversion.json | 55 +++ .../disabled/good/partial.json | 1 + .../valid-fpd-allowed-with-ext-bidder.json | 3 +- .../valid-fpd-allowed-with-prebid-bidder.json | 3 +- .../valid-native/asset-img-no-hmin.json | 25 +- .../valid-native/asset-img-no-wmin.json | 25 +- .../valid-native/asset-with-id.json | 1 + .../valid-native/asset-with-no-id.json | 1 + .../valid-native/assets-with-unique-ids.json | 1 + .../context-product-compatible-subtype.json | 25 +- .../context-social-compatible-subtype.json | 25 +- .../eventtracker-exchange-specific.json | 3 +- .../valid-native/request-no-context.json | 1 + .../valid-native/request-plcmttype-empty.json | 1 + .../video-asset-event-tracker.json | 1 + .../valid-native/with-video-asset.json | 1 + .../valid-whole/exemplary/all-ext.json | 1 + .../valid-whole/exemplary/prebid-test-ad.json | 1 + .../valid-whole/exemplary/skadn.json | 3 +- errortypes/code.go | 2 + exchange/bidder_test.go | 20 +- exchange/exchange.go | 30 +- exchange/exchange_test.go | 395 +++++++++++++++++- go.mod | 2 +- go.sum | 3 +- openrtb_ext/request.go | 7 + 50 files changed, 1761 insertions(+), 106 deletions(-) create mode 100644 currency/aggregate_conversions.go create mode 100644 currency/aggregate_conversions_test.go create mode 100644 currency/errors.go create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json diff --git a/currency/aggregate_conversions.go b/currency/aggregate_conversions.go new file mode 100644 index 00000000000..53c5ebff4b6 --- /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.(ConversionRateNotFound); !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..35ca51a1fe7 --- /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: ConversionRateNotFound{"GBP", "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..dde317d809e 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, ConversionRateNotFound{fromUnit.String(), toUnit.String()} } return 1, nil diff --git a/currency/errors.go b/currency/errors.go new file mode 100644 index 00000000000..d764c15b984 --- /dev/null +++ b/currency/errors.go @@ -0,0 +1,13 @@ +package currency + +import "fmt" + +// ConversionRateNotFound 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 ConversionRateNotFound struct { + FromCur, ToCur string +} + +func (err ConversionRateNotFound) 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..62914c4b2e2 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,8 +44,11 @@ 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 +// 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 MissingConversionRate 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) { var err error fromUnit, err := currency.ParseISO(from) @@ -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, ConversionRateNotFound{fromUnit.String(), toUnit.String()} } return 0, errors.New("rates are nil") } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 8913e90791d..d8a7fa689b9 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 @@ -343,6 +344,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { if err := deps.validateEidPermissions(bidExt, aliases); err != nil { return []error{err} } + + if err := validateCustomRates(bidExt.Prebid.CurrencyConversions); err != nil { + return []error{err} + } } if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { @@ -437,6 +442,30 @@ func validateSChains(req *openrtb_ext.ExtRequest) error { return err } +// 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 + } + + 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(req *openrtb_ext.ExtRequest, aliases map[string]string) error { if req == nil || req.Prebid.Data == nil { return nil diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 3d40d35b068..bcdac13dc06 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -24,6 +24,7 @@ import ( "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" @@ -45,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) { @@ -105,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)) @@ -248,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) @@ -441,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{}, @@ -1184,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 @@ -2590,7 +2719,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 @@ -2601,6 +2772,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 { @@ -2625,9 +2826,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, + }, + }, + } } } } @@ -2640,6 +2849,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/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/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 index 501e7ef5016..214031177ca 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json +++ b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json @@ -22,6 +22,7 @@ "id": "req-id", "bidid": "test bid id", "nbr": 0, + "cur": "USD", "seatbid": [{ "bid": [{ "id": "appnexus-bid", @@ -32,4 +33,4 @@ }] }, "expectedReturnCode": 200 -} \ No newline at end of file +} 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/errortypes/code.go b/errortypes/code.go index 2749b978006..869e7d541a4 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -11,6 +11,7 @@ const ( BidderTemporarilyDisabledErrorCode BlacklistedAcctErrorCode AcctRequiredErrorCode + NoConversionRateErrorCode ) // Defines numeric codes for well-known warnings. @@ -19,6 +20,7 @@ const ( InvalidPrivacyConsentWarningCode = iota + 10000 AccountLevelDebugDisabledWarningCode BidderLevelDebugDisabledWarningCode + DisabledCurrencyConversionWarningCode ) // Coder provides an error or warning code with severity. diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 3140930d8e6..5fdfe445206 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -564,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.ConversionRateNotFound{"JPY", "USD"}, }, description: "Case 6 - Bidder respond with a mix of currencies and one unknown on all HTTP responses", }, @@ -587,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.ConversionRateNotFound{"JPY", "USD"}, + currency.ConversionRateNotFound{"BZD", "USD"}, + currency.ConversionRateNotFound{"DKK", "USD"}, }, description: "Case 7 - Bidder respond with currencies not having any rate on all HTTP responses", }, @@ -719,9 +719,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.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionRateNotFound{"EUR", "USD"}, + currency.ConversionRateNotFound{"EUR", "USD"}, }, description: "Case 2 - Bidder respond with the same currency (not default one) on all HTTP responses", }, @@ -753,7 +753,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.ConversionRateNotFound{"EUR", "USD"}, }, description: "Case 7 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -761,7 +761,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.ConversionRateNotFound{"GBP", "USD"}, }, description: "Case 8 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -769,7 +769,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.ConversionRateNotFound{"GBP", "USD"}, }, description: "Case 9 - Bidder responds with a mix of not set and empty currencies (default currency) in HTTP responses", }, diff --git a/exchange/exchange.go b/exchange/exchange.go index ba70305f660..c1602aadfcb 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -202,7 +202,7 @@ 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, r.GlobalPrivacyControlHeader) @@ -972,6 +972,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 d3bcf082cf2..f778e3ba411 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -86,8 +86,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), } @@ -95,7 +95,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)) @@ -114,7 +114,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" @@ -147,10 +147,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) } @@ -507,6 +507,366 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { } +func TestOverrideWithCustomCurrency(t *testing.T) { + + mockCurrencyClient := &mockCurrencyRatesClient{ + 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 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 := &mockCurrencyRatesClient{ + 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 := "" @@ -709,7 +1069,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{ @@ -730,7 +1090,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() @@ -747,7 +1107,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, @@ -849,10 +1209,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{ @@ -3251,3 +3611,16 @@ type nilCategoryFetcher struct{} func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { return "", nil } + +// mockCurrencyRatesClient is a simple http client mock returning a constant response body +type mockCurrencyRatesClient struct { + responseBody string +} + +func (m *mockCurrencyRatesClient) Do(req *http.Request) (*http.Response, error) { + return &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(strings.NewReader(m.responseBody)), + }, nil +} diff --git a/go.mod b/go.mod index e3b9ff556d5..6d1fe334390 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,6 @@ 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 215f5e68e28..78b21ae139c 100644 --- a/go.sum +++ b/go.sum @@ -169,8 +169,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= diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index f14cf196366..606874f196a 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -41,6 +41,13 @@ type ExtRequestPrebid struct { // passing of personally identifiable information doesn't constitute a sale per CCPA law. // The array may contain a single sstar ('*') entry to represent all bidders. NoSale []string `json:"nosale,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 From 613de7e052dcf0933e42fd59183defc4ccb3d2d5 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 10 Jun 2021 12:27:37 -0700 Subject: [PATCH 012/140] Debug override header (#1853) --- config/config.go | 2 + config/config_test.go | 1 + endpoints/openrtb2/video_auction.go | 18 ++--- endpoints/openrtb2/video_auction_test.go | 6 +- exchange/auction.go | 27 +++++--- exchange/auction_test.go | 50 ++++++++++++++ exchange/bidder.go | 28 +++++--- exchange/bidder_test.go | 18 +++-- exchange/bidder_validate_bids.go | 4 +- exchange/bidder_validate_bids_test.go | 10 +-- exchange/cachetest/debuglog_enabled.json | 2 + exchange/exchange.go | 19 +++--- exchange/exchange_test.go | 68 +++++++++++++------ exchange/exchangetest/debuglog_enabled.json | 2 + .../debuglog_enabled_no_bids.json | 2 + 15 files changed, 186 insertions(+), 71 deletions(-) diff --git a/config/config.go b/config/config.go index a259a9aa4e1..e8dd94a00a5 100644 --- a/config/config.go +++ b/config/config.go @@ -449,6 +449,7 @@ type DefReqFiles struct { type Debug struct { TimeoutNotification TimeoutNotification `mapstructure:"timeout_notification"` + OverrideToken string `mapstructure:"override_token"` } func (cfg *Debug) validate(errs []error) []error { @@ -988,6 +989,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 diff --git a/config/config_test.go b/config/config_test.go index 1d4c00a5cd1..84d3b4794a9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -421,6 +421,7 @@ func TestFullConfig(t *testing.T) { 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) { diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 84f0699e011..0af3ba512bb 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 } @@ -306,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) @@ -344,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..9f0859a32cd 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) diff --git a/exchange/auction.go b/exchange/auction.go index 3d733daaff8..94e808801d9 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 54f67eb8177..ee064fcb6f1 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -118,6 +118,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 c6e2587452a..83466c7d3b0 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. @@ -126,7 +126,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 { @@ -176,19 +176,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 5fdfe445206..deff066200a 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{ { @@ -208,7 +208,7 @@ func TestSetGPCHeader(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{GlobalPrivacyControlHeader: "1"}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -246,7 +246,7 @@ func TestSetGPCHeaderNil(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{GlobalPrivacyControlHeader: "1"}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) expectedHttpCall := []*openrtb_ext.ExtHttpCall{ { @@ -304,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.") @@ -681,6 +681,7 @@ func TestMultiCurrencies(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) // Verify: @@ -826,6 +827,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) // Verify: @@ -999,6 +1001,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + false, ) // Verify: @@ -1303,6 +1306,7 @@ func TestMobileNativeTypes(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) var actualValue string @@ -1316,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.") } @@ -1537,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..aec0948ddde 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...) } diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index 3bb43559856..06973b837c2 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -42,7 +42,7 @@ func TestAllValidBids(t *testing.T) { }, }, }) - 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, 3) assert.Len(t, errs, 0) } @@ -83,7 +83,7 @@ func TestAllBadBids(t *testing.T) { }, }, }) - 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) } @@ -126,7 +126,7 @@ func TestMixedBids(t *testing.T) { }, }, }) - 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, 2) assert.Len(t, errs, 3) } @@ -246,7 +246,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 +257,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/exchange.go b/exchange/exchange.go index c1602aadfcb..6f0c610a958 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -169,13 +169,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) @@ -204,7 +204,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * // Get currency rates conversions for the auction conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) - adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader) + adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride) var auc *auction var cacheErrs []error @@ -249,7 +249,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 { @@ -270,7 +270,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) @@ -281,7 +281,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", @@ -414,7 +414,8 @@ func (e *exchange) getAllBids( bidAdjustments map[string]float64, conversions currency.Conversions, accountDebugAllowed bool, - globalPrivacyControlHeader string) ( + globalPrivacyControlHeader string, + headerDebugAllowed bool) ( map[openrtb_ext.BidderName]*pbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, bool) { // Set up pointers to the bid results @@ -446,7 +447,7 @@ func (e *exchange) getAllBids( var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader - bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed) + 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) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index f778e3ba411..cd36ad94b22 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -176,8 +176,9 @@ func TestDebugBehaviour(t *testing.T) { } type debugData struct { - bidderLevelDebugAllowed bool - accountLevelDebugAllowed bool + bidderLevelDebugAllowed bool + accountLevelDebugAllowed bool + headerOverrideDebugAllowed bool } type aTest struct { @@ -192,57 +193,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, }, } @@ -322,9 +344,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) @@ -338,6 +363,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) @@ -357,13 +387,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 { @@ -3411,7 +3441,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 { @@ -3590,7 +3620,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!") } 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": { From ccb56efe1f23ee101aef3fc604db799a1282f7f9 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Fri, 11 Jun 2021 10:58:47 -0400 Subject: [PATCH 013/140] Remove GDPR TCF1 (#1854) --- config/config.go | 44 +++--- config/config_test.go | 81 +++++++++- exchange/utils_test.go | 57 +++---- gdpr/gdpr.go | 4 +- gdpr/impl.go | 57 +++---- gdpr/impl_test.go | 215 +++++++++++++++----------- gdpr/vendorlist-fetching.go | 28 +--- gdpr/vendorlist-fetching_test.go | 173 ++++----------------- metrics/go_metrics_test.go | 10 -- metrics/metrics.go | 4 - metrics/prometheus/prometheus_test.go | 15 -- privacy/gdpr/policy_test.go | 5 - static/tcf1/fallback_gvl.json | 1 - 13 files changed, 296 insertions(+), 398 deletions(-) delete mode 100644 static/tcf1/fallback_gvl.json diff --git a/config/config.go b/config/config.go index e8dd94a00a5..7e24a00d370 100644 --- a/config/config.go +++ b/config/config.go @@ -197,7 +197,6 @@ type GDPR struct { 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. @@ -218,9 +217,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 } @@ -237,20 +233,14 @@ 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 PurposeDetail `mapstructure:"purpose1"` + Purpose2 PurposeDetail `mapstructure:"purpose2"` + Purpose7 PurposeDetail `mapstructure:"purpose7"` + SpecialPurpose1 PurposeDetail `mapstructure:"special_purpose1"` + PurposeOneTreatment PurposeOneTreatment `mapstructure:"purpose_one_treatment"` } // Making a purpose struct so purpose specific details can be added later. @@ -258,7 +248,7 @@ type PurposeDetail struct { Enabled bool `mapstructure:"enabled"` } -type PurposeOneTreatement struct { +type PurposeOneTreatment struct { Enabled bool `mapstructure:"enabled"` AccessAllowed bool `mapstructure:"access_allowed"` } @@ -951,16 +941,12 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) - v.SetDefault("gdpr.tcf1.fetch_gvl", false) - v.SetDefault("gdpr.tcf1.fallback_gvl_path", "./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.purpose4.enabled", true) v.SetDefault("gdpr.tcf2.purpose7.enabled", true) v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true) - v.SetDefault("gdpr.tcf2.purpose_one_treatement.enabled", true) - v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true) v.SetDefault("gdpr.amp_exception", false) v.SetDefault("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", @@ -1015,6 +1001,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) { @@ -1030,6 +1020,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 84d3b4794a9..f34bd5fa189 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -141,6 +141,8 @@ func TestDefaults(t *testing.T) { 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) + cmpBools(t, "gdpr.tcf2.purpose_one_treatment.enabled", true, cfg.GDPR.TCF2.PurposeOneTreatment.Enabled) + cmpBools(t, "gdpr.tcf2.purpose_one_treatment.access_allowed", true, cfg.GDPR.TCF2.PurposeOneTreatment.AccessAllowed) } var fullConfig = []byte(` @@ -510,6 +512,79 @@ func TestMigrateConfigFromEnv(t *testing.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, "") @@ -569,12 +644,6 @@ func TestInvalidHostVendorID(t *testing.T) { } } -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.GDPR.AMPException = true diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 50636d35ccd..1788d508c31 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1447,8 +1447,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { } } -func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { - tcf1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY" +func TestCleanOpenRTBRequestsGDPR(t *testing.T) { tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" trueValue, falseValue := true, false @@ -1477,19 +1476,7 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - 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", @@ -1501,11 +1488,11 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - description: "Not Enforce - TCF 1", + description: "Not Enforce", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "0", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, @@ -1513,36 +1500,36 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - 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, 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, 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, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, @@ -1550,23 +1537,23 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { }, }, { - 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, 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, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, @@ -1578,12 +1565,12 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "null", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, userSyncIfAmbiguous: false, expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { @@ -1591,7 +1578,7 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "null", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, userSyncIfAmbiguous: true, expectPrivacyLabels: metrics.PrivacyLabels{ @@ -1604,12 +1591,12 @@ func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, permissionsError: errors.New("Some error"), expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, } diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index ffd5ced462a..4179d8122ac 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -30,7 +30,6 @@ type Permissions interface { // Versions of the GDPR TCF technical specification. const ( - tcf1SpecVersion uint8 = 1 tcf2SpecVersion uint8 = 2 ) @@ -44,8 +43,7 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ cfg: cfg, 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 a91a9308e24..aca07fd068d 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" @@ -116,23 +116,15 @@ 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, false), nil } func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { @@ -141,44 +133,33 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, 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 parsedConsent.Version() == 2 { - if p.cfg.TCF2.Enabled { - return p.allowActivitiesTCF2(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 true, false, false, nil -} -func (p *permissionsImpl) allowActivitiesTCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { - consent, ok := parsedConsent.(tcf2.ConsentMetadata) + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) if !ok { err = fmt.Errorf("Unable to access TCF2 parsed consent") return } if p.cfg.TCF2.SpecialPurpose1.Enabled { - passGeo = consent.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) + passGeo = consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) } else { passGeo = true } if p.cfg.TCF2.Purpose2.Enabled { - allowBidRequest = p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(2), weakVendorEnforcement) + allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), weakVendorEnforcement) } else { allowBidRequest = true } for i := 2; i <= 10; i++ { - if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i), weakVendorEnforcement) { + if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), weakVendorEnforcement) { passID = true break } @@ -191,8 +172,8 @@ 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, 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) { @@ -224,7 +205,7 @@ 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()) diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 3b974ffa3bb..d26c0c57231 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -23,7 +23,6 @@ func TestDisallowOnEmptyConsent(t *testing.T) { }, vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: failedListFetcher, tcf2SpecVersion: failedListFetcher, }, } @@ -49,106 +48,120 @@ 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.PurposeDetail{ + 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, "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.PurposeDetail{ + 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.PurposeDetail{ + 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, "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 +172,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), }, } @@ -172,7 +184,7 @@ func TestMalformedConsent(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 @@ -190,7 +202,7 @@ func TestAllowActivities(t *testing.T) { publisherID: "appNexusAppID", userSyncIfAmbiguous: false, gdpr: SignalYes, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -198,7 +210,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderBlockedByConsent, userSyncIfAmbiguous: false, gdpr: SignalNo, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -206,7 +218,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderAllowedByConsent, userSyncIfAmbiguous: false, gdpr: SignalYes, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -222,7 +234,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderAllowedByConsent, userSyncIfAmbiguous: true, gdpr: SignalAmbiguous, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -238,7 +250,7 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderAllowedByConsent, userSyncIfAmbiguous: false, gdpr: SignalAmbiguous, - consent: consent, + consent: vendor2AndPurpose2Consent, passID: true, }, { @@ -254,31 +266,37 @@ func TestAllowActivities(t *testing.T) { bidderName: bidderBlockedByConsent, userSyncIfAmbiguous: false, gdpr: SignalYes, - consent: consent, + 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.PurposeDetail{ + Enabled: 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), }), }, } @@ -293,10 +311,10 @@ func TestAllowActivities(t *testing.T) { } } -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,7 +350,7 @@ func buildTCF2VendorList34() tcf2VendorList { } } -var tcf2Config = config.GDPR{ +var gdprConfig = config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, @@ -343,7 +361,7 @@ var tcf2Config = config.GDPR{ }, } -type tcf2TestDef struct { +type testDef struct { description string bidder openrtb_ext.BidderName consent string @@ -353,10 +371,10 @@ type tcf2TestDef struct { weakVendorEnforcement bool } -func TestAllowActivitiesTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowActivitiesGeoAndID(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, @@ -364,7 +382,6 @@ func TestAllowActivitiesTCF2(t *testing.T) { 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), @@ -372,8 +389,8 @@ func TestAllowActivitiesTCF2(t *testing.T) { }, } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8 - 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, @@ -429,17 +446,16 @@ func TestAllowActivitiesTCF2(t *testing.T) { } } -func TestAllowActivitiesWhitelistTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowActivitiesWhitelist(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, 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), }), @@ -453,17 +469,16 @@ func TestAllowActivitiesWhitelistTCF2(t *testing.T) { assert.EqualValuesf(t, true, passID, "PassID failure") } -func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowActivitiesPubRestrict(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, 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), }), @@ -472,7 +487,7 @@ func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { // 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, @@ -504,24 +519,23 @@ func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { } } -func TestAllowSyncTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestAllowSync(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, 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), }), }, } - // 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") @@ -531,19 +545,18 @@ 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) +func TestProhibitedPurposeSync(t *testing.T) { + vendorList34 := buildVendorList34() + vendorList34.Vendors["8"].Purposes = []int{7} + vendorListData := MarshalVendorList(vendorList34) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, 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), }), @@ -551,7 +564,7 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { } perms.cfg.HostVendorID = 8 - // 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") @@ -561,10 +574,10 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } -func TestProhibitedVendorSyncTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) +func TestProhibitedVendorSync(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ - cfg: tcf2Config, + cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, @@ -572,7 +585,6 @@ func TestProhibitedVendorSyncTCF2(t *testing.T) { 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), }), @@ -580,7 +592,7 @@ func TestProhibitedVendorSyncTCF2(t *testing.T) { } perms.cfg.HostVendorID = 10 - // 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") @@ -713,7 +725,7 @@ func TestNormalizeGDPR(t *testing.T) { } } -func TestAllowActivitiesTCF2BidRequests(t *testing.T) { +func TestAllowActivitiesBidRequests(t *testing.T) { purpose2AndVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAADAQAAAAAA" purpose2ConsentWithoutVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAABIAAAAA" @@ -757,7 +769,7 @@ func TestAllowActivitiesTCF2BidRequests(t *testing.T) { } for _, td := range testDefs { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) + vendorListData := MarshalVendorList(buildVendorList34()) perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, @@ -773,7 +785,6 @@ func TestAllowActivitiesTCF2BidRequests(t *testing.T) { openrtb_ext.BidderPubmatic: 6, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), }), @@ -787,3 +798,21 @@ func TestAllowActivitiesTCF2BidRequests(t *testing.T) { 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") +} 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/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 7c0a3f377e2..61930bf54f0 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]) } @@ -548,25 +547,16 @@ 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") } diff --git a/metrics/metrics.go b/metrics/metrics.go index 7cb5f0f1d88..5912a151bca 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -299,7 +299,6 @@ type TCFVersionValue string const ( TCFVersionErr TCFVersionValue = "err" - TCFVersionV1 TCFVersionValue = "v1" TCFVersionV2 TCFVersionValue = "v2" ) @@ -307,7 +306,6 @@ const ( func TCFVersions() []TCFVersionValue { return []TCFVersionValue{ TCFVersionErr, - TCFVersionV1, TCFVersionV2, } } @@ -315,8 +313,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 } diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 72ddd105152..087ee570551 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -1390,18 +1390,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), @@ -1436,13 +1428,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{ diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go index dc8f56425c5..a0fa6241d72 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", 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 From a9ee429449797f9a7fdf48a47c295fd5377cbf85 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:26:55 -0400 Subject: [PATCH 014/140] Rename GDPR UserSyncIfAmbiguous to DefaultValue (#1858) --- config/config.go | 9 +- config/config_test.go | 13 ++- endpoints/cookie_sync.go | 6 +- endpoints/cookie_sync_test.go | 4 +- exchange/exchange.go | 58 +++++------ exchange/exchange_test.go | 37 ++++--- exchange/targeting_test.go | 18 ++-- exchange/utils.go | 4 +- exchange/utils_test.go | 63 ++++++------ gdpr/impl.go | 2 +- gdpr/impl_test.go | 176 +++++++++++++++++----------------- 11 files changed, 209 insertions(+), 181 deletions(-) diff --git a/config/config.go b/config/config.go index 7e24a00d370..1260d28a779 100644 --- a/config/config.go +++ b/config/config.go @@ -193,7 +193,7 @@ 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{} @@ -202,12 +202,15 @@ type GDPR struct { // 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 { + 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)) } @@ -937,7 +940,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("amp_timeout_adjustment_ms", 0) v.SetDefault("gdpr.enabled", true) v.SetDefault("gdpr.host_vendor_id", 0) - v.SetDefault("gdpr.usersync_if_ambiguous", false) + v.SetDefault("gdpr.default_value", "1") 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{""}) diff --git a/config/config_test.go b/config/config_test.go index f34bd5fa189..a2b28d026d7 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -148,7 +148,7 @@ func TestDefaults(t *testing.T) { var fullConfig = []byte(` gdpr: host_vendor_id: 15 - usersync_if_ambiguous: true + default_value: "0" non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] ccpa: enforce: true @@ -352,7 +352,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, "0") //Assert the NonStandardPublishers was correctly unmarshalled cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[0], "siteID") @@ -460,6 +460,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{ @@ -650,6 +653,12 @@ func TestInvalidAMPException(t *testing.T) { 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)") } +func TestInvalidGDPRDefaultValue(t *testing.T) { + cfg := newDefaultConfig(t) + cfg.GDPR.DefaultValue = "2" + assertOneError(t, cfg.validate(), "gdpr.default_value must be 0 or 1") +} + func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { cfg := Configuration{ CurrencyConverter: CurrencyConverter{ diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index bf3935f0535..3c1354c86bd 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) @@ -181,7 +181,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()) } @@ -193,7 +193,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 7632894baf6..2f56c262979 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -110,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") @@ -149,7 +149,7 @@ func TestCookieSyncNoBidders(t *testing.T) { } 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())) diff --git a/exchange/exchange.go b/exchange/exchange.go index 6f0c610a958..7b156b5dee2 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -51,18 +51,18 @@ 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 + 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 string + privacyConfig config.Privacy + categoriesFetcher stored_requests.CategoryFetcher + bidIDGenerator BidIDGenerator } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -111,16 +111,16 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool { 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 { 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: cfg.GDPR.DefaultValue, privacyConfig: config.Privacy{ CCPA: cfg.CCPA, GDPR: cfg.GDPR, @@ -186,10 +186,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, e.me, 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) @@ -301,8 +301,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) string { + gdprDefaultValue := e.gdprDefaultValue var geo *openrtb2.Geo = nil if bidRequest.User != nil && bidRequest.User.Geo != nil { @@ -314,14 +314,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 = "1" } else if len(geo.Country) == 3 { // The country field is formatted properly as a three character country code - usersyncIfAmbiguous = true + gdprDefaultValue = "0" } } - return usersyncIfAmbiguous + return gdprDefaultValue } func recordImpMetrics(bidRequest *openrtb2.BidRequest, metricsEngine metrics.MetricsEngine) { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index cd36ad94b22..5a54b5e14d9 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2004,6 +2004,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, @@ -2012,9 +2019,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{} @@ -2156,18 +2163,18 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] } return &exchange{ - 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)), - 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: privacyConfig.GDPR.DefaultValue, + privacyConfig: privacyConfig, + categoriesFetcher: categoriesFetcher, + bidderInfo: bidderInfos, + externalURL: "http://localhost", + bidIDGenerator: bidIDGenerator, } } diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index aa07ed0c77b..86646957091 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: "1", + categoriesFetcher: categoriesFetcher, + bidIDGenerator: &mockBidIDGenerator{false, false}, } imps := buildImps(t, mockBids) diff --git a/exchange/utils.go b/exchange/utils.go index 3d5e2374008..c5cf673250d 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -57,7 +57,7 @@ func cleanOpenRTBRequests(ctx context.Context, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, metricsEngine metrics.MetricsEngine, - usersyncIfAmbiguous bool, + gdprDefaultValue string, privacyConfig config.Privacy, account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { @@ -87,7 +87,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 == "1") ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &req.Account, aliases, integrationTypeMap[req.LegacyLabels.RType]) if err != nil { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 1788d508c31..dad0d69db15 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -479,7 +479,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { for _, test := range testCases { metricsMock := metrics.MetricsEngineMock{} permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, true, privacyConfig, nil) + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, "0", privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -636,7 +636,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { nil, &permissionsMock{allowAllBidders: true, passGeo: true, passID: true}, &metrics.MetricsEngineMock{}, - true, + "0", privacyConfig, nil) result := bidderRequests[0] @@ -698,7 +698,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { } permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, true, privacyConfig, nil) + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, "0", privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -740,7 +740,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, true, config.Privacy{}, nil) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -849,7 +849,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, true, config.Privacy{}, nil) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, "0", config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1432,7 +1432,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, true, privacyConfig, nil) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1459,7 +1459,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdprConsent string gdprScrub bool permissionsError error - userSyncIfAmbiguous bool + gdprDefaultValue string expectPrivacyLabels metrics.PrivacyLabels expectError bool }{ @@ -1470,6 +1470,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: "malformed", gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: "", @@ -1482,6 +1483,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1494,6 +1496,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "0", gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1506,6 +1509,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "0{", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1519,6 +1523,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1531,6 +1536,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1543,6 +1549,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1555,32 +1562,33 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", 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: tcf2Consent, - 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.TCFVersionV2, }, }, { - description: "Not Enforce - Ambiguous signal, sync user if ambiguous", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "null", - gdprConsent: tcf2Consent, - 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: "", @@ -1594,6 +1602,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdprConsent: tcf2Consent, gdprScrub: true, permissionsError: errors.New("Some error"), + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, @@ -1610,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, }, @@ -1636,7 +1645,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { nil, &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, &metrics.MetricsEngineMock{}, - test.userSyncIfAmbiguous, + test.gdprDefaultValue, privacyConfig, nil) result := results[0] @@ -1698,8 +1707,8 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { privacyConfig := config.Privacy{ GDPR: config.GDPR{ - Enabled: test.gdprEnforced, - UsersyncIfAmbiguous: true, + Enabled: test.gdprEnforced, + DefaultValue: "0", TCF2: config.TCF2{ Enabled: true, }, @@ -1727,7 +1736,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { nil, &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, &metricsMock, - true, + "0", privacyConfig, nil) diff --git a/gdpr/impl.go b/gdpr/impl.go index aca07fd068d..9c09e90b58e 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -94,7 +94,7 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return gdprSignal } - if p.cfg.UsersyncIfAmbiguous { + if p.cfg.DefaultValue == "0" { return SignalNo } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index d26c0c57231..345dd52621d 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -18,8 +18,8 @@ import ( func TestDisallowOnEmptyConsent(t *testing.T) { perms := permissionsImpl{ cfg: config.GDPR{ - HostVendorID: 3, - UsersyncIfAmbiguous: true, + HostVendorID: 3, + DefaultValue: "0", }, vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ @@ -190,84 +190,84 @@ func TestAllowActivities(t *testing.T) { description string bidderName openrtb_ext.BidderName publisherID string - userSyncIfAmbiguous bool + gdprDefaultValue string gdpr Signal consent string passID bool weakVendorEnforcement bool }{ { - description: "Allow PI - Non standard publisher", - bidderName: bidderBlockedByConsent, - publisherID: "appNexusAppID", - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: vendor2AndPurpose2Consent, - passID: 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: vendor2AndPurpose2Consent, - passID: 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: vendor2AndPurpose2Consent, - passID: 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: "", - passID: 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: vendor2AndPurpose2Consent, - passID: 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: "", - passID: 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: vendor2AndPurpose2Consent, - passID: 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: "", - passID: 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: vendor2AndPurpose2Consent, - passID: 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 := MarshalVendorList(vendorList{ @@ -302,7 +302,7 @@ func TestAllowActivities(t *testing.T) { } for _, tt := range tests { - perms.cfg.UsersyncIfAmbiguous = tt.userSyncIfAmbiguous + perms.cfg.DefaultValue = tt.gdprDefaultValue _, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) @@ -669,53 +669,53 @@ 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, }, } From 037bd7f19920e01ee2ee2504410b9b84f134bf92 Mon Sep 17 00:00:00 2001 From: Rachel Joyce Date: Tue, 15 Jun 2021 10:19:30 -0600 Subject: [PATCH 015/140] Accept bidfloor from impression to fix issue #1787 for sovrn adapter (#1886) --- adapters/sovrn/sovrn.go | 5 +- .../both-custom-default-bidfloor.json | 126 ++++++++++++++++++ .../sovrntest/supplemental/no-bidfloor.json | 122 +++++++++++++++++ .../supplemental/only-custom-bidfloor.json | 125 +++++++++++++++++ .../supplemental/only-default-bidfloor.json | 124 +++++++++++++++++ 5 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/no-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json 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" + } + ] + } + ] +} From f894d18ee447ab35f7822295c207ec24cf0a1086 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 16 Jun 2021 15:23:16 -0400 Subject: [PATCH 016/140] GDPR: require host specify default value (#1859) --- config/config.go | 14 ++++--- config/config_test.go | 76 +++++++++++++++++++++++--------------- endpoints/auction_test.go | 1 + exchange/exchange.go | 15 +++++--- exchange/exchange_test.go | 7 +++- exchange/targeting_test.go | 2 +- exchange/utils.go | 4 +- exchange/utils_test.go | 21 +++++++---- gdpr/gdpr.go | 10 ++++- gdpr/impl.go | 9 +++-- gdpr/impl_test.go | 14 ++++++- 11 files changed, 113 insertions(+), 60 deletions(-) diff --git a/config/config.go b/config/config.go index 1260d28a779..5621c2ec416 100644 --- a/config/config.go +++ b/config/config.go @@ -93,7 +93,7 @@ type HTTPClient struct { IdleConnTimeout int `mapstructure:"idle_connection_timeout_seconds"` } -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) @@ -105,7 +105,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) @@ -207,8 +207,10 @@ type GDPR struct { EEACountriesMap map[string]struct{} } -func (cfg *GDPR) validate(errs []error) []error { - if cfg.DefaultValue != "0" && cfg.DefaultValue != "1" { +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 { @@ -520,7 +522,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) } @@ -938,9 +940,9 @@ 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", "1") 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{""}) diff --git a/config/config_test.go b/config/config_test.go index a2b28d026d7..fa9dcdc5195 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -116,10 +116,7 @@ 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") + cfg, _ := newDefaultConfig(t) cmpInts(t, "port", cfg.Port, 8000) cmpInts(t, "admin_port", cfg.AdminPort, 6060) @@ -148,7 +145,7 @@ func TestDefaults(t *testing.T) { var fullConfig = []byte(` gdpr: host_vendor_id: 15 - default_value: "0" + default_value: "1" non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] ccpa: enforce: true @@ -352,7 +349,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) - cmpStrings(t, "gdpr.default_value", cfg.GDPR.DefaultValue, "0") + 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") @@ -429,6 +426,7 @@ func TestFullConfig(t *testing.T) { 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) @@ -484,14 +482,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) @@ -508,10 +510,7 @@ 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) } @@ -591,6 +590,7 @@ func TestMigrateConfigPurposeOneTreatment(t *testing.T) { 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) @@ -600,6 +600,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) @@ -607,16 +608,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) { @@ -638,9 +639,9 @@ 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) @@ -648,34 +649,47 @@ func TestInvalidHostVendorID(t *testing.T) { } 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 := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.GDPR.DefaultValue = "2" - assertOneError(t, cfg.validate(), "gdpr.default_value must be 0 or 1") + 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) } @@ -733,6 +747,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: @@ -750,31 +765,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/endpoints/auction_test.go b/endpoints/auction_test.go index 2062589e895..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()) diff --git a/exchange/exchange.go b/exchange/exchange.go index 7b156b5dee2..e96aadbce86 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -59,7 +59,7 @@ type exchange struct { gDPR gdpr.Permissions currencyConverter *currency.RateConverter externalURL string - gdprDefaultValue string + gdprDefaultValue gdpr.Signal privacyConfig config.Privacy categoriesFetcher stored_requests.CategoryFetcher bidIDGenerator BidIDGenerator @@ -110,6 +110,11 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool { } 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, @@ -120,7 +125,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid externalURL: cfg.ExternalURL, gDPR: gDPR, me: metricsEngine, - gdprDefaultValue: cfg.GDPR.DefaultValue, + gdprDefaultValue: gdprDefaultValue, privacyConfig: config.Privacy{ CCPA: cfg.CCPA, GDPR: cfg.GDPR, @@ -301,7 +306,7 @@ 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) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) string { +func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) gdpr.Signal { gdprDefaultValue := e.gdprDefaultValue var geo *openrtb2.Geo = nil @@ -314,10 +319,10 @@ func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) string // 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 { - gdprDefaultValue = "1" + gdprDefaultValue = gdpr.SignalYes } else if len(geo.Country) == 3 { // The country field is formatted properly as a three character country code - gdprDefaultValue = "0" + gdprDefaultValue = gdpr.SignalNo } } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 5a54b5e14d9..2d1306af093 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2162,6 +2162,11 @@ 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()), @@ -2169,7 +2174,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] cacheTime: 0, gDPR: &permissionsMock{allowAllBidders: true}, currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - gdprDefaultValue: privacyConfig.GDPR.DefaultValue, + gdprDefaultValue: gdprDefaultValue, privacyConfig: privacyConfig, categoriesFetcher: categoriesFetcher, bidderInfo: bidderInfos, diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 86646957091..f38a6c0266c 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -93,7 +93,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op cacheTime: time.Duration(0), gDPR: gdpr.AlwaysAllow{}, currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - gdprDefaultValue: "1", + gdprDefaultValue: gdpr.SignalYes, categoriesFetcher: categoriesFetcher, bidIDGenerator: &mockBidIDGenerator{false, false}, } diff --git a/exchange/utils.go b/exchange/utils.go index c5cf673250d..3def0425819 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -57,7 +57,7 @@ func cleanOpenRTBRequests(ctx context.Context, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, metricsEngine metrics.MetricsEngine, - gdprDefaultValue string, + gdprDefaultValue gdpr.Signal, privacyConfig config.Privacy, account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { @@ -87,7 +87,7 @@ func cleanOpenRTBRequests(ctx context.Context, if err != nil { errs = append(errs, err) } - gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == "1") + 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 { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index dad0d69db15..5a281f9a360 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -479,7 +479,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { for _, test := range testCases { metricsMock := metrics.MetricsEngineMock{} permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, "0", privacyConfig, nil) + 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 { @@ -636,7 +636,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { nil, &permissionsMock{allowAllBidders: true, passGeo: true, passID: true}, &metrics.MetricsEngineMock{}, - "0", + gdpr.SignalNo, privacyConfig, nil) result := bidderRequests[0] @@ -698,7 +698,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { } permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, "0", privacyConfig, nil) + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -740,7 +740,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", config.Privacy{}, nil) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -849,7 +849,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, "0", config.Privacy{}, nil) + 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) @@ -1432,7 +1432,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, "0", privacyConfig, nil) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1639,13 +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{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, &metrics.MetricsEngineMock{}, - test.gdprDefaultValue, + gdprDefaultValue, privacyConfig, nil) result := results[0] @@ -1736,7 +1741,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { nil, &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, &metricsMock, - "0", + gdpr.SignalNo, privacyConfig, nil) diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 4179d8122ac..47d20a50899 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -39,9 +39,15 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ return &AlwaysAllow{} } + gdprDefaultValue := SignalYes + if cfg.DefaultValue == "0" { + gdprDefaultValue = SignalNo + } + permissionsImpl := &permissionsImpl{ - cfg: cfg, - vendorIDs: vendorIDs, + cfg: cfg, + gdprDefaultValue: gdprDefaultValue, + vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)}, } diff --git a/gdpr/impl.go b/gdpr/impl.go index 9c09e90b58e..af7150a8755 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -28,9 +28,10 @@ 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 + 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) { @@ -94,7 +95,7 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return gdprSignal } - if p.cfg.DefaultValue == "0" { + if p.gdprDefaultValue == SignalNo { return SignalNo } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 345dd52621d..f4b0392876b 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -21,7 +21,8 @@ func TestDisallowOnEmptyConsent(t *testing.T) { HostVendorID: 3, DefaultValue: "0", }, - vendorIDs: nil, + gdprDefaultValue: SignalNo, + vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: failedListFetcher, }, @@ -303,6 +304,11 @@ func TestAllowActivities(t *testing.T) { for _, tt := range tests { perms.cfg.DefaultValue = tt.gdprDefaultValue + if tt.gdprDefaultValue == "0" { + perms.gdprDefaultValue = SignalNo + } else { + perms.gdprDefaultValue = SignalYes + } _, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) @@ -719,6 +725,12 @@ func TestNormalizeGDPR(t *testing.T) { }, } + if tt.gdprDefaultValue == "0" { + perms.gdprDefaultValue = SignalNo + } else { + perms.gdprDefaultValue = SignalYes + } + normalizedSignal := perms.normalizeGDPR(tt.giveSignal) assert.Equal(t, tt.wantSignal, normalizedSignal, tt.description) From ad3e22f0b194dc970b24f5c0616b96e3dcd092a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20DEYM=C3=88S?= <47388595+MaxSmileWanted@users.noreply.github.com> Date: Wed, 16 Jun 2021 21:33:10 +0200 Subject: [PATCH 017/140] New Adapter: Smile Wanted (#1877) * New Adapter: Smile Wanted * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-676968474 * Improvement of test coverage as requested. * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-683853119 --- adapters/smilewanted/params_test.go | 58 ++++++++++ adapters/smilewanted/smilewanted.go | 106 ++++++++++++++++++ adapters/smilewanted/smilewanted_test.go | 20 ++++ .../exemplary/simple-banner.json | 94 ++++++++++++++++ .../exemplary/simple-video.json | 87 ++++++++++++++ .../smilewantedtest/params/race/banner.json | 3 + .../smilewantedtest/params/race/video.json | 3 + .../supplemental/bad-server-response.json | 63 +++++++++++ .../supplemental/status-code-204.json | 59 ++++++++++ .../supplemental/status-code-400.json | 64 +++++++++++ .../supplemental/unexpected-status-code.json | 64 +++++++++++ adapters/smilewanted/usersync.go | 12 ++ adapters/smilewanted/usersync_test.go | 34 ++++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/smilewanted.yaml | 12 ++ static/bidder-params/smilewanted.json | 14 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 20 files changed, 702 insertions(+) create mode 100644 adapters/smilewanted/params_test.go create mode 100644 adapters/smilewanted/smilewanted.go create mode 100644 adapters/smilewanted/smilewanted_test.go create mode 100644 adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json create mode 100644 adapters/smilewanted/smilewantedtest/exemplary/simple-video.json create mode 100644 adapters/smilewanted/smilewantedtest/params/race/banner.json create mode 100644 adapters/smilewanted/smilewantedtest/params/race/video.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json create mode 100644 adapters/smilewanted/usersync.go create mode 100644 adapters/smilewanted/usersync_test.go create mode 100644 static/bidder-info/smilewanted.yaml create mode 100644 static/bidder-params/smilewanted.json 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/config/config.go b/config/config.go index 5621c2ec416..58bb40a592c 100644 --- a/config/config.go +++ b/config/config.go @@ -636,6 +636,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") @@ -906,6 +907,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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index a773d268604..1fdb7c2489d 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -94,6 +94,7 @@ import ( "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" @@ -215,6 +216,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.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, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ea6c809fd9b..91ce9f0d27f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -166,6 +166,7 @@ const ( BidderSmartAdserver BidderName = "smartadserver" BidderSmartRTB BidderName = "smartrtb" BidderSmartyAds BidderName = "smartyads" + BidderSmileWanted BidderName = "smilewanted" BidderSomoaudience BidderName = "somoaudience" BidderSonobi BidderName = "sonobi" BidderSovrn BidderName = "sovrn" @@ -286,6 +287,7 @@ func CoreBidderNames() []BidderName { BidderSmartAdserver, BidderSmartRTB, BidderSmartyAds, + BidderSmileWanted, BidderSomoaudience, BidderSonobi, BidderSovrn, 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-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/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 88752f4d7d7..169417e07e8 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -73,6 +73,7 @@ import ( "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" @@ -174,6 +175,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) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 2ebd541d015..0cea346e423 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -82,6 +82,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, From 6a642757a486a1bdee464deaeac924ed444ab520 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Thu, 17 Jun 2021 11:36:31 -0400 Subject: [PATCH 018/140] Fix a weak vendor enforcement bug where vendor does not exist (#1890) --- gdpr/impl.go | 26 +++++++++++++++++++++++++- gdpr/impl_test.go | 18 ++++++++++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/gdpr/impl.go b/gdpr/impl.go index af7150a8755..94c37e61d96 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -136,7 +136,11 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, // 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 !p.cfg.TCF2.Enabled { @@ -209,6 +213,7 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons if version != 2 { return } + vendorList, err := p.fetchVendorList[version](ctx, parsedConsent.VendorListVersion()) if err != nil { return @@ -242,3 +247,22 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B 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 } + +// vendorTrue claims everything. +type vendorTrue struct{} + +func (v vendorTrue) Purpose(purposeID consentconstants.Purpose) bool { + return true +} +func (v vendorTrue) PurposeStrict(purposeID consentconstants.Purpose) bool { + return true +} +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 f4b0392876b..d1a6cad75e8 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -382,10 +382,11 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { perms := permissionsImpl{ cfg: gdprConfig, vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - openrtb_ext.BidderOpenx: 20, + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 20, + openrtb_ext.BidderAudienceNetwork: 55, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ @@ -414,6 +415,15 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { 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, From 96353641c64537a2b866dd0ea61b43f3957377ea Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Thu, 17 Jun 2021 21:28:22 +0530 Subject: [PATCH 019/140] Pubmatic: Sending GPT slotname in impression extension (#1880) --- adapters/pubmatic/pubmatic.go | 27 ++- .../supplemental/gptSlotNameInImpExt.json | 167 ++++++++++++++++++ .../gptSlotNameInImpExtPbAdslot.json | 163 +++++++++++++++++ 3 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index dc1ee9d0dc4..c2e9fffa0fe 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -56,6 +56,21 @@ type pubmaticBidExt struct { VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` } +type ExtImpBidderPubmatic struct { + adapters.ExtImpBidder + Data *ExtData `json:"data,omitempty"` +} + +type ExtData struct { + AdServer *ExtAdServer `json:"adserver"` + PBAdSlot string `json:"pbadslot"` +} + +type ExtAdServer struct { + Name string `json:"name"` + AdSlot string `json:"adslot"` +} + const ( INVALID_PARAMS = "Invalid BidParam" MISSING_PUBID = "Missing PubID" @@ -70,6 +85,8 @@ const ( dctrKeyName = "key_val" pmZoneIDKeyName = "pmZoneId" pmZoneIDKeyNameOld = "pmZoneID" + ImpExtAdUnitKey = "dfp_ad_unit_code" + AdServerGAM = "gam" ) func PrepareLogMessage(tID, pubId, adUnitId, bidID, details string, args ...interface{}) string { @@ -453,7 +470,7 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er imp.Audio = nil } - var bidderExt adapters.ExtImpBidder + var bidderExt ExtImpBidderPubmatic if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return err } @@ -501,6 +518,14 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er extMap[pmZoneIDKeyName] = pubmaticExt.PmZoneID } + if bidderExt.Data != nil { + if bidderExt.Data.AdServer != nil && bidderExt.Data.AdServer.Name == AdServerGAM && bidderExt.Data.AdServer.AdSlot != "" { + extMap[ImpExtAdUnitKey] = bidderExt.Data.AdServer.AdSlot + } else if bidderExt.Data.PBAdSlot != "" { + extMap[ImpExtAdUnitKey] = bidderExt.Data.PBAdSlot + } + } + imp.Ext = nil if len(extMap) > 0 { ext, err := json.Marshal(extMap) diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json new file mode 100644 index 00000000000..50f677c8c0b --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json @@ -0,0 +1,167 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": "999", + "keywords": [ + { + "key": "pmZoneID", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + }, + "data": { + "adserver": { + "name": "gam", + "adslot": "/1111/home" + }, + "pbadslot": "/2222/home" + } + } + } + ], + "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", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "ext": { + "pmZoneId": "Zone1,Zone2", + "preference": "sports,movies", + "dfp_ad_unit_code": "/1111/home" + } + } + ], + "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", + "h": 250, + "w": 300, + "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/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json new file mode 100644 index 00000000000..cc057909a5b --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json @@ -0,0 +1,163 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": "999", + "keywords": [ + { + "key": "pmZoneID", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + }, + "data": { + "pbadslot": "/2222/home" + } + } + } + ], + "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", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "ext": { + "pmZoneId": "Zone1,Zone2", + "preference": "sports,movies", + "dfp_ad_unit_code": "/2222/home" + } + } + ], + "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", + "h": 250, + "w": 300, + "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 From 4b8c1bd39f9ecf233e474246f33ced1d68279781 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Sat, 19 Jun 2021 16:37:48 -0400 Subject: [PATCH 020/140] Update To Go 1.16 (#1888) --- .devcontainer/devcontainer.json | 4 ++-- .github/workflows/validate-merge.yml | 2 +- .github/workflows/validate.yml | 2 +- Dockerfile | 4 ++-- README.md | 2 +- go.mod | 3 +-- go.sum | 2 -- 7 files changed, 8 insertions(+), 11 deletions(-) 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/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 f3338ce1613..64486fd4393 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`. diff --git a/go.mod b/go.mod index 6d1fe334390..7d10a5c9bca 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 @@ -22,7 +22,6 @@ 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 diff --git a/go.sum b/go.sum index 78b21ae139c..4cb5863dd41 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,6 @@ github.com/influxdata/influxdb v1.6.1 h1:OseoBlzI5ftNI/bczyxSWq6PKRCNEeiXvyWP/wS github.com/influxdata/influxdb v1.6.1/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4EI5MABq68= 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/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= From 9489942cc0bc837e01384ca78caea307cfbcb81d Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 22 Jun 2021 20:53:25 -0400 Subject: [PATCH 021/140] Friendlier Startup Error Messages (#1894) --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index ebeabf29df8..5c5a2a462e0 100644 --- a/main.go +++ b/main.go @@ -34,12 +34,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) } } From e24356fdbde9d4a54edccbdd137209cb833bee43 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Wed, 23 Jun 2021 12:51:40 -0400 Subject: [PATCH 022/140] Second fix for weak vendor enforcement (#1896) --- gdpr/impl.go | 2 ++ gdpr/impl_test.go | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gdpr/impl.go b/gdpr/impl.go index 94c37e61d96..17a1a893e1c 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -81,6 +81,8 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, if id, ok := p.vendorIDs[bidder]; ok { return p.allowActivities(ctx, id, consent, weakVendorEnforcement) + } else if weakVendorEnforcement { + return p.allowActivities(ctx, 0, consent, weakVendorEnforcement) } return p.defaultVendorPermissions() diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index d1a6cad75e8..cde485467b3 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -382,11 +382,10 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { perms := permissionsImpl{ cfg: gdprConfig, 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, + 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){ tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ From 74d84d52fd33deba117bdb4818a6e1401172380a Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Thu, 24 Jun 2021 19:51:35 +0300 Subject: [PATCH 023/140] Rubicon: hardcode EUR to USD for floors (#1899) Co-authored-by: Serhii Nahornyi --- adapters/rubicon/rubicon.go | 15 ++++++ adapters/rubicon/rubicon_test.go | 46 +++++++++++++++++++ .../rubicontest/exemplary/simple-video.json | 4 ++ 3 files changed, 65 insertions(+) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 89d69522fe8..73f6a5d39ca 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -750,6 +750,10 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } + resolvedBidFloor, resolvedBidFloorCur := resolveBidFloorAttributes(thisImp.BidFloor, thisImp.BidFloorCur) + thisImp.BidFloorCur = resolvedBidFloorCur + thisImp.BidFloor = resolvedBidFloor + if request.User != nil { userCopy := *request.User userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}} @@ -893,6 +897,17 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada return requestData, errs } +// Will be replaced after https://github.com/prebid/prebid-server/issues/1482 resolution +func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, string) { + if bidFloor > 0 { + if strings.ToUpper(bidFloorCur) == "EUR" { + return bidFloor * 1.2, "USD" + } + } + + return bidFloor, bidFloorCur +} + func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error { var segmentIdsToCopy = make([]string, 0) diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index dc5b3a90423..8f8d3fb1557 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -572,6 +572,52 @@ func TestResolveVideoSizeId(t *testing.T) { } } +func TestResolveBidFloorAttributes(t *testing.T) { + testScenarios := []struct { + bidFloor float64 + bidFloorCur string + expectedBidFloor float64 + expectedBidFloorCur string + }{ + { + bidFloor: 1, + bidFloorCur: "EUR", + expectedBidFloor: 1.2, + expectedBidFloorCur: "USD", + }, + { + bidFloor: 1, + bidFloorCur: "Eur", + expectedBidFloor: 1.2, + expectedBidFloorCur: "USD", + }, + { + bidFloor: 0, + bidFloorCur: "EUR", + expectedBidFloor: 0, + expectedBidFloorCur: "EUR", + }, + { + bidFloor: -1, + bidFloorCur: "EUR", + expectedBidFloor: -1, + expectedBidFloorCur: "EUR", + }, + { + bidFloor: 1, + bidFloorCur: "USD", + expectedBidFloor: 1, + expectedBidFloorCur: "USD", + }, + } + + for _, scenario := range testScenarios { + bidFloor, bidFloorCur := resolveBidFloorAttributes(scenario.bidFloor, scenario.bidFloorCur) + assert.Equal(t, scenario.expectedBidFloor, bidFloor) + assert.Equal(t, scenario.expectedBidFloorCur, bidFloorCur) + } +} + func TestNoContentResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index b85c28def44..a90670e53be 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -71,6 +71,8 @@ "w": 1024, "h": 576 }, + "bidfloor": 1, + "bidfloorcur": "EuR", "ext": { "bidder": { "video": { @@ -192,6 +194,8 @@ "w": 1024, "h": 576 }, + "bidfloor": 1.2, + "bidfloorcur": "USD", "ext": { "rp": { "track": { From 952a1c9120bba7215f390987105779fb8bb13c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Thu, 24 Jun 2021 19:26:58 +0200 Subject: [PATCH 024/140] Outbrain adapter: overwrite tagid only if it exists (#1895) --- adapters/outbrain/outbrain.go | 6 +- .../supplemental/general_params.json | 139 ++++++++++++++++++ .../supplemental/optional_params.json | 3 + 3 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 adapters/outbrain/outbraintest/supplemental/general_params.json 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" }, From beaf6432f6926b0f73c16da4d964a8d29fc5e4e0 Mon Sep 17 00:00:00 2001 From: bidmyadz <82382704+bidmyadz@users.noreply.github.com> Date: Wed, 30 Jun 2021 18:02:31 +0300 Subject: [PATCH 025/140] New Adapter: BidMyAdz (#1882) Co-authored-by: BidMyAdz --- adapters/bidmyadz/bidmyadz.go | 157 +++++++++++++++++ adapters/bidmyadz/bidmyadz_test.go | 18 ++ .../bidmyadztest/exemplary/banner.json | 146 ++++++++++++++++ .../bidmyadztest/exemplary/native.json | 141 +++++++++++++++ .../bidmyadztest/exemplary/video.json | 160 ++++++++++++++++++ .../bidmyadztest/params/race/banner.json | 3 + .../bidmyadztest/params/race/native.json | 3 + .../bidmyadztest/params/race/video.json | 3 + .../supplemental/invalid-device-fields.json | 48 ++++++ .../supplemental/invalid-multi-imps.json | 61 +++++++ .../supplemental/missing-mediatype.json | 122 +++++++++++++ .../supplemental/response-without-bids.json | 109 ++++++++++++ .../response-without-seatbid.json | 106 ++++++++++++ .../bidmyadztest/supplemental/status-204.json | 94 ++++++++++ .../bidmyadztest/supplemental/status-400.json | 101 +++++++++++ .../status-service-unavailable.json | 100 +++++++++++ .../supplemental/status-unknown.json | 101 +++++++++++ adapters/bidmyadz/params_test.go | 49 ++++++ adapters/bidmyadz/usersync.go | 12 ++ adapters/bidmyadz/usersync_test.go | 33 ++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/bidmyadz.yaml | 13 ++ static/bidder-params/bidmyadz.json | 12 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 27 files changed, 1601 insertions(+) create mode 100644 adapters/bidmyadz/bidmyadz.go create mode 100644 adapters/bidmyadz/bidmyadz_test.go create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/banner.json create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/native.json create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/video.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/banner.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/native.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/video.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-204.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-400.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json create mode 100644 adapters/bidmyadz/params_test.go create mode 100644 adapters/bidmyadz/usersync.go create mode 100644 adapters/bidmyadz/usersync_test.go create mode 100644 static/bidder-info/bidmyadz.yaml create mode 100644 static/bidder-params/bidmyadz.json diff --git a/adapters/bidmyadz/bidmyadz.go b/adapters/bidmyadz/bidmyadz.go new file mode 100644 index 00000000000..829d57e606f --- /dev/null +++ b/adapters/bidmyadz/bidmyadz.go @@ -0,0 +1,157 @@ +package bidmyadz + +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, +) { + + var errors []error + + if len(openRTBRequest.Imp) > 1 { + errors = append(errors, &errortypes.BadInput{ + Message: "Bidder does not support multi impression", + }) + } + + if openRTBRequest.Device.IP == "" && openRTBRequest.Device.IPv6 == "" { + errors = append(errors, &errortypes.BadInput{ + Message: "IP/IPv6 is a required field", + }) + } + + if openRTBRequest.Device.UA == "" { + errors = append(errors, &errortypes.BadInput{ + Message: "User-Agent is a required field", + }) + } + + if len(errors) != 0 { + return nil, errors + } + + 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") + headers.Add("X-Openrtb-Version", "2.5") + + 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.BadServerResponse{ + Message: "Bidder is unavailable. Please contact your account manager.", + }} + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Something went wrong. 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: fmt.Sprintf("BidExt parsing error. %s", err.Error()), + }} + } + + 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/bidmyadz/bidmyadz_test.go b/adapters/bidmyadz/bidmyadz_test.go new file mode 100644 index 00000000000..b0de6a02956 --- /dev/null +++ b/adapters/bidmyadz/bidmyadz_test.go @@ -0,0 +1,18 @@ +package bidmyadz + +import ( + "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 TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderBidmyadz, config.Adapter{ + Endpoint: "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "bidmyadztest", bidder) +} diff --git a/adapters/bidmyadz/bidmyadztest/exemplary/banner.json b/adapters/bidmyadz/bidmyadztest/exemplary/banner.json new file mode 100644 index 00000000000..460291bc4f0 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/exemplary/banner.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/exemplary/native.json b/adapters/bidmyadz/bidmyadztest/exemplary/native.json new file mode 100644 index 00000000000..984802601c0 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/exemplary/native.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "111", + "tmax": 150, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "ip": "71.106.52.124", + "ua": "Mozilla/5.0 (X11; Linux x86_64; Ubuntu 14.04.2 LTS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36" + }, + "user": { + "id": "user-id" + }, + "site": { + "id": "native", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + }, + "cur": [ + "USD" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.01, + "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\":15}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":300,\"hmin\":300,\"type\":3}}, {\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "tnative" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "111", + "tmax": 150, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "ip": "71.106.52.124", + "ua": "Mozilla/5.0 (X11; Linux x86_64; Ubuntu 14.04.2 LTS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36" + }, + "user": { + "id": "user-id" + }, + "site": { + "id": "native", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + }, + "cur": [ + "USD" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.01, + "bidfloorcur": "USD", + "secure": 1, + "native": { + "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":15}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":300,\"hmin\":300,\"type\":3}}, {\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "tnative" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [{ + "bid": [{ + "id": "1", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{native-ads}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "2", + "w": 0, + "h": 0, + "ext": { + "mediaType": "native" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1", + "impid": "1", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "{native-ads}", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "2", + "ext": { + "mediaType": "native" + } + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/exemplary/video.json b/adapters/bidmyadz/bidmyadztest/exemplary/video.json new file mode 100644 index 00000000000..92c42f8331a --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/exemplary/video.json @@ -0,0 +1,160 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 5, + "maxduration": 60, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 300, + "h": 250, + "linearity": 1, + "playbackmethod": [2] + }, + "bidfloor": 0.001, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "placementId": "tvideo" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "video": { + "mimes": ["video/mp4", "video/ogg", "video/webm"], + "minduration": 5, + "maxduration": 60, + "protocols": [2, 3, 5, 6, 7, 8], + "w": 300, + "h": 250, + "linearity": 1, + "playbackmethod": [2] + }, + "bidfloor": 0.001, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "placementId": "tvideo" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "video" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250, + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/banner.json b/adapters/bidmyadz/bidmyadztest/params/race/banner.json new file mode 100644 index 00000000000..18dce42f2c4 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "placementId": "tbanner" +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/native.json b/adapters/bidmyadz/bidmyadztest/params/race/native.json new file mode 100644 index 00000000000..0600af3a894 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "placementId": "tnative" +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/video.json b/adapters/bidmyadz/bidmyadztest/params/race/video.json new file mode 100644 index 00000000000..85478bf22c5 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "placementId": "tvideo" +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json new file mode 100644 index 00000000000..d620a050632 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "IP/IPv6 is a required field", + "comparison": "literal" + }, { + "value": "User-Agent is a required field", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json new file mode 100644 index 00000000000..09020fc89e9 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }, { + "id": "1", + "secure": 1, + "bidfloor": 0.11, + "bidfloorcur": "USD", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "placementId": "3234" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "Bidder does not support multi impression", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json b/adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json new file mode 100644 index 00000000000..486d7324dfc --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://test.com/nurl", + "burl": "http://test.com/burl", + "adm": "", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "1", + "crid": "1", + "w": 300, + "h": 250 + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "BidExt parsing error. unexpected end of JSON input", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json new file mode 100644 index 00000000000..fe3361f69d8 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid.Bids", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json new file mode 100644 index 00000000000..727e745e762 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json @@ -0,0 +1,106 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "a66e61ff8c2ab5460ecf156c4b892fd84d41fad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "1" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [], + "cur": "USD" + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-204.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-204.json new file mode 100644 index 00000000000..05efda0e9f3 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-204.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-400.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-400.json new file mode 100644 index 00000000000..e24acdc6766 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-400.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "Source blocked" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad Request. \"Source blocked\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json new file mode 100644 index 00000000000..13e22f55889 --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json @@ -0,0 +1,100 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bidder is unavailable. Please contact your account manager.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json b/adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json new file mode 100644 index 00000000000..69b649e19ad --- /dev/null +++ b/adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc", + "body": { + "id": "12345", + "imp": [{ + "id": "1", + "secure": 1, + "bidfloor": 0.31, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "tbanner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "ip": "18.146.18.241", + "devicetype": 2 + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5" + }, + "site": { + "id": "banner", + "domain": "test.com", + "cat": ["IAB1"], + "page": "https://test.com", + "publisher": { + "id": "test" + } + } + } + }, + "mockResponse": { + "status": 403, + "body": "Forbidden" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong. Status Code: [ 403 ] \"Forbidden\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/bidmyadz/params_test.go b/adapters/bidmyadz/params_test.go new file mode 100644 index 00000000000..857cde86d22 --- /dev/null +++ b/adapters/bidmyadz/params_test.go @@ -0,0 +1,49 @@ +package bidmyadz + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "placementId": "1234" }`, +} + +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.BidderBidmyadz, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected bidmyadz params: %s", validParam) + } + } +} + +var invalidParams = []string{ + `1234`, + ``, + `true`, + `null`, + `[]`, + `{}`, + `{ "anyparam": "anyvalue" }`, + `{ "placementId": null }`, +} + +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.BidderBidmyadz, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/bidmyadz/usersync.go b/adapters/bidmyadz/usersync.go new file mode 100644 index 00000000000..755a184d6e4 --- /dev/null +++ b/adapters/bidmyadz/usersync.go @@ -0,0 +1,12 @@ +package bidmyadz + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewBidmyadzSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("bidmyadz", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/bidmyadz/usersync_test.go b/adapters/bidmyadz/usersync_test.go new file mode 100644 index 00000000000..11b5fedd73f --- /dev/null +++ b/adapters/bidmyadz/usersync_test.go @@ -0,0 +1,33 @@ +package bidmyadz + +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 TestNewBidmyadzSyncer(t *testing.T) { + syncURL := "https://test.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + syncer := NewBidmyadzSyncer(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/c0f68227d14ed938c6c49f3967cbe9bc?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 58bb40a592c..47db6b57b9c 100644 --- a/config/config.go +++ b/config/config.go @@ -587,6 +587,7 @@ 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") @@ -842,6 +843,7 @@ func SetupViper(v *viper.Viper, filename string) { 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 1fdb7c2489d..dd6638126c9 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -33,6 +33,7 @@ import ( "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" @@ -154,6 +155,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.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, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 91ce9f0d27f..d6075bb15b5 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -104,6 +104,7 @@ const ( BidderBeintoo BidderName = "beintoo" BidderBetween BidderName = "between" BidderBidmachine BidderName = "bidmachine" + BidderBidmyadz BidderName = "bidmyadz" BidderBidsCube BidderName = "bidscube" BidderBmtm BidderName = "bmtm" BidderBrightroll BidderName = "brightroll" @@ -225,6 +226,7 @@ func CoreBidderNames() []BidderName { BidderBeintoo, BidderBetween, BidderBidmachine, + BidderBidmyadz, BidderBidsCube, BidderBmtm, BidderBrightroll, 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-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/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 169417e07e8..7de59491e02 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -28,6 +28,7 @@ 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" @@ -128,6 +129,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync 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) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 0cea346e423..d86efea30d8 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -37,6 +37,7 @@ 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, From c0a638cfaff9aca69c5a410eaebfe131df4485a1 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 30 Jun 2021 12:54:40 -0400 Subject: [PATCH 026/140] Currency Conversion Utility Function (#1901) --- adapters/bidder.go | 20 ++++++ adapters/bidder_test.go | 63 +++++++++++++++++ currency/aggregate_conversions.go | 2 +- currency/aggregate_conversions_test.go | 2 +- currency/constant_rates.go | 2 +- currency/errors.go | 6 +- currency/rates.go | 6 +- exchange/bidder_test.go | 20 +++--- exchange/exchange.go | 2 +- exchange/exchange_test.go | 94 ++++++++++++++++++++++++-- 10 files changed, 192 insertions(+), 25 deletions(-) create mode 100644 adapters/bidder_test.go diff --git a/adapters/bidder.go b/adapters/bidder.go index 2e5ec83c849..b7bde4bc55d 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -7,6 +7,7 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -138,6 +139,25 @@ func (r *RequestData) SetBasicAuth(username string, password string) { type ExtraRequestInfo struct { PbsEntryPoint metrics.RequestType GlobalPrivacyControlHeader string + currencyConversions currency.Conversions +} + +func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { + return ExtraRequestInfo{ + currencyConversions: c, + } +} + +// ConvertCurrency converts a given amount from one currency to another, or returns: +// - Error if the `from` or `to` arguments are malformed or unknown ISO-4217 codes. +// - ConversionNotFoundError if the conversion mapping is unknown to Prebid Server +// and not provided in the bid request. +func (r ExtraRequestInfo) ConvertCurrency(value float64, from, to string) (float64, error) { + if rate, err := r.currencyConversions.GetRate(from, to); err == nil { + return value * rate, nil + } else { + return 0, err + } } type Builder func(openrtb_ext.BidderName, config.Adapter) (Bidder, error) diff --git a/adapters/bidder_test.go b/adapters/bidder_test.go new file mode 100644 index 00000000000..f0b833cec2e --- /dev/null +++ b/adapters/bidder_test.go @@ -0,0 +1,63 @@ +package adapters + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestExtraRequestInfoConvertCurrency(t *testing.T) { + var ( + givenValue float64 = 2 + givenFrom string = "AAA" + givenTo string = "BBB" + ) + + testCases := []struct { + description string + setMock func(m *mock.Mock) + expectedValue float64 + expectedError error + }{ + { + description: "Success", + setMock: func(m *mock.Mock) { m.On("GetRate", "AAA", "BBB").Return(2.5, nil) }, + expectedValue: 5, + expectedError: nil, + }, + { + description: "Error", + setMock: func(m *mock.Mock) { m.On("GetRate", "AAA", "BBB").Return(2.5, errors.New("some error")) }, + expectedValue: 0, + expectedError: errors.New("some error"), + }, + } + + for _, test := range testCases { + mockConversions := &mockConversions{} + test.setMock(&mockConversions.Mock) + + extraRequestInfo := NewExtraRequestInfo(mockConversions) + result, err := extraRequestInfo.ConvertCurrency(givenValue, givenFrom, givenTo) + + mockConversions.AssertExpectations(t) + assert.Equal(t, test.expectedValue, result, test.description+":result") + assert.Equal(t, test.expectedError, err, test.description+":err") + } +} + +type mockConversions struct { + mock.Mock +} + +func (m mockConversions) GetRate(from string, to string) (float64, error) { + args := m.Called(from, to) + return args.Get(0).(float64), args.Error(1) +} + +func (m mockConversions) GetRates() *map[string]map[string]float64 { + args := m.Called() + return args.Get(0).(*map[string]map[string]float64) +} diff --git a/currency/aggregate_conversions.go b/currency/aggregate_conversions.go index 53c5ebff4b6..a15404fe501 100644 --- a/currency/aggregate_conversions.go +++ b/currency/aggregate_conversions.go @@ -23,7 +23,7 @@ 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.(ConversionRateNotFound); !isMissingRateErr { + } else if _, isMissingRateErr := err.(ConversionNotFoundError); !isMissingRateErr { // other error, return the error return 0, err } diff --git a/currency/aggregate_conversions_test.go b/currency/aggregate_conversions_test.go index 35ca51a1fe7..773a596c28c 100644 --- a/currency/aggregate_conversions_test.go +++ b/currency/aggregate_conversions_test.go @@ -65,7 +65,7 @@ func TestGroupedGetRate(t *testing.T) { }, }, { - expectedError: ConversionRateNotFound{"GBP", "EUR"}, + expectedError: ConversionNotFoundError{FromCur: "GBP", ToCur: "EUR"}, testCases: []aTest{ {"Valid three-digit currency codes, but conversion rate not found", "GBP", "EUR", 0}, }, diff --git a/currency/constant_rates.go b/currency/constant_rates.go index dde317d809e..ccc5c24d3fc 100644 --- a/currency/constant_rates.go +++ b/currency/constant_rates.go @@ -27,7 +27,7 @@ func (r *ConstantRates) GetRate(from string, to string) (float64, error) { } if fromUnit.String() != toUnit.String() { - return 0, ConversionRateNotFound{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 index d764c15b984..bb4c42aa90a 100644 --- a/currency/errors.go +++ b/currency/errors.go @@ -2,12 +2,12 @@ package currency import "fmt" -// ConversionRateNotFound is thrown by the currency.Conversions GetRate(from string, to string) method +// 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 ConversionRateNotFound struct { +type ConversionNotFoundError struct { FromCur, ToCur string } -func (err ConversionRateNotFound) Error() 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 62914c4b2e2..b9cb0201b38 100644 --- a/currency/rates.go +++ b/currency/rates.go @@ -47,9 +47,9 @@ func (r *Rates) UnmarshalJSON(b []byte) 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 MissingConversionRate error in case the conversion rate between the two +// - A ConversionNotFoundError 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) { +func (r *Rates) GetRate(from, to string) (float64, error) { var err error fromUnit, err := currency.ParseISO(from) if err != nil { @@ -70,7 +70,7 @@ func (r *Rates) GetRate(from string, to string) (float64, error) { // In case we have an entry TO -> FROM return 1 / conversion, nil } - return 0, ConversionRateNotFound{fromUnit.String(), toUnit.String()} + return 0, ConversionNotFoundError{FromCur: fromUnit.String(), ToCur: toUnit.String()} } return 0, errors.New("rates are nil") } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index deff066200a..213af9a0f05 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -564,7 +564,7 @@ func TestMultiCurrencies(t *testing.T) { {currency: "USD", price: 1.3 * 1.3050530256}, }, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"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", }, @@ -587,9 +587,9 @@ func TestMultiCurrencies(t *testing.T) { }, expectedBids: []bid{}, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"JPY", "USD"}, - currency.ConversionRateNotFound{"BZD", "USD"}, - currency.ConversionRateNotFound{"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", }, @@ -720,9 +720,9 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "EUR", "EUR"}, expectedBidsCount: 0, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"EUR", "USD"}, - currency.ConversionRateNotFound{"EUR", "USD"}, - currency.ConversionRateNotFound{"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", }, @@ -754,7 +754,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"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", }, @@ -762,7 +762,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"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", }, @@ -770,7 +770,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", ""}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - currency.ConversionRateNotFound{"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", }, diff --git a/exchange/exchange.go b/exchange/exchange.go index e96aadbce86..6aaafcd1ad6 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -449,7 +449,7 @@ 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 reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 2d1306af093..2a61a4e8454 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -31,6 +31,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" ) @@ -539,7 +540,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { func TestOverrideWithCustomCurrency(t *testing.T) { - mockCurrencyClient := &mockCurrencyRatesClient{ + mockCurrencyClient := &fakeCurrencyRatesHttpClient{ responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, } mockCurrencyConverter := currency.NewRateConverter( @@ -694,6 +695,72 @@ func TestOverrideWithCustomCurrency(t *testing.T) { } } +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{ @@ -859,7 +926,7 @@ func TestGetAuctionCurrencyRates(t *testing.T) { } // Init mock currency conversion service - mockCurrencyClient := &mockCurrencyRatesClient{ + mockCurrencyClient := &fakeCurrencyRatesHttpClient{ responseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`, } mockCurrencyConverter := currency.NewRateConverter( @@ -3654,15 +3721,32 @@ func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, return "", nil } -// mockCurrencyRatesClient is a simple http client mock returning a constant response body -type mockCurrencyRatesClient struct { +// fakeCurrencyRatesHttpClient is a simple http client mock returning a constant response body +type fakeCurrencyRatesHttpClient struct { responseBody string } -func (m *mockCurrencyRatesClient) Do(req *http.Request) (*http.Response, error) { +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) +} From 8335d83bef45b6e249a845c2d68ec1062f78f527 Mon Sep 17 00:00:00 2001 From: lunamedia <73552749+lunamedia@users.noreply.github.com> Date: Wed, 30 Jun 2021 23:44:01 +0300 Subject: [PATCH 027/140] New Adapter: SA Lunamedia (#1891) --- adapters/sa_lunamedia/params_test.go | 52 ++++++ adapters/sa_lunamedia/salunamedia.go | 132 ++++++++++++++ adapters/sa_lunamedia/salunamedia_test.go | 18 ++ .../salunamediatest/exemplary/banner.json | 142 +++++++++++++++ .../salunamediatest/exemplary/native.json | 132 ++++++++++++++ .../salunamediatest/exemplary/video.json | 169 ++++++++++++++++++ .../salunamediatest/params/race/banner.json | 3 + .../salunamediatest/params/race/native.json | 3 + .../salunamediatest/params/race/video.json | 3 + .../supplemental/bad-response.json | 98 ++++++++++ .../supplemental/empty-seatbid.json | 102 +++++++++++ .../supplemental/status-204.json | 92 ++++++++++ .../supplemental/status-400.json | 99 ++++++++++ .../supplemental/status-503.json | 98 ++++++++++ .../supplemental/unexpected-status.json | 99 ++++++++++ adapters/sa_lunamedia/usersync.go | 12 ++ adapters/sa_lunamedia/usersync_test.go | 33 ++++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_sa_lunamedia.go | 6 + static/bidder-info/sa_lunamedia.yaml | 14 ++ static/bidder-params/sa_lunamedia.json | 17 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 25 files changed, 1333 insertions(+) create mode 100644 adapters/sa_lunamedia/params_test.go create mode 100644 adapters/sa_lunamedia/salunamedia.go create mode 100644 adapters/sa_lunamedia/salunamedia_test.go create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/banner.json create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/native.json create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/video.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/banner.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/native.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/video.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json create mode 100644 adapters/sa_lunamedia/usersync.go create mode 100644 adapters/sa_lunamedia/usersync_test.go create mode 100644 openrtb_ext/imp_sa_lunamedia.go create mode 100644 static/bidder-info/sa_lunamedia.yaml create mode 100644 static/bidder-params/sa_lunamedia.json 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/config/config.go b/config/config.go index 47db6b57b9c..f6fa9917178 100644 --- a/config/config.go +++ b/config/config.go @@ -618,6 +618,7 @@ func (cfg *Configuration) setDerivedDefaults() { 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. @@ -880,6 +881,7 @@ func SetupViper(v *viper.Viper, filename string) { 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index dd6638126c9..17e0d4f54eb 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -89,6 +89,7 @@ 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" @@ -191,6 +192,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.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, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index d6075bb15b5..f2dd9815e8e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -140,6 +140,7 @@ const ( BidderLockerDome BidderName = "lockerdome" BidderLogicad BidderName = "logicad" BidderLunaMedia BidderName = "lunamedia" + BidderSaLunaMedia BidderName = "sa_lunamedia" BidderMadvertise BidderName = "madvertise" BidderMarsmedia BidderName = "marsmedia" BidderMediafuse BidderName = "mediafuse" @@ -262,6 +263,7 @@ func CoreBidderNames() []BidderName { BidderLockerDome, BidderLogicad, BidderLunaMedia, + BidderSaLunaMedia, BidderMadvertise, BidderMarsmedia, BidderMediafuse, 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/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-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/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 7de59491e02..7472bf3a70d 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -70,6 +70,7 @@ 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" @@ -156,6 +157,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync 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) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index d86efea30d8..81216b19199 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -65,6 +65,7 @@ func TestNewSyncerMap(t *testing.T) { 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, From b996cf9c01565e86e5517650c623a9dc6943d260 Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu, 1 Jul 2021 08:49:34 -0700 Subject: [PATCH 028/140] Removed Digitrust From Prebid Server (#1892) Co-authored-by: avolcy --- .../adformtest/supplemental/user-nil.json | 7 +- adapters/dmx/dmx.go | 2 +- adapters/dmx/dmx_test.go | 77 +++++-------------- adapters/rubicon/rubicon.go | 14 ++-- adapters/rubicon/rubicon_test.go | 10 +-- .../rubicontest/exemplary/simple-video.json | 1 - endpoints/openrtb2/amp_auction_test.go | 12 +-- endpoints/openrtb2/auction.go | 6 +- .../invalid-whole/digitrust.json | 46 ----------- .../valid-whole/supplementary/digitrust.json | 50 ------------ exchange/exchange_test.go | 4 +- .../exchangetest/request-other-user-ext.json | 14 +--- .../exchangetest/request-user-no-prebid.json | 10 --- exchange/utils.go | 2 +- exchange/utils_test.go | 4 +- openrtb_ext/user.go | 13 ---- privacy/scrubber.go | 5 +- privacy/scrubber_test.go | 35 +++------ 18 files changed, 46 insertions(+), 266 deletions(-) delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json delete mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json 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/dmx/dmx.go b/adapters/dmx/dmx.go index 7124d229347..adcec4a33c5 100644 --- a/adapters/dmx/dmx.go +++ b/adapters/dmx/dmx.go @@ -148,7 +148,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt } if dmxReq.User.Ext != nil { if err := json.Unmarshal(dmxReq.User.Ext, &userExt); err == nil { - if len(userExt.Eids) > 0 || (userExt.DigiTrust != nil && userExt.DigiTrust.ID != "") { + if len(userExt.Eids) > 0 { anyHasId = true } } diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index a9f1e8bbc79..409290c110d 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -643,50 +643,6 @@ func TestUserEidsOnly(t *testing.T) { } } -func TestUserDigitrustOnly(t *testing.T) { - var w, h int = 300, 250 - - var width, height int64 = int64(w), int64(h) - - bidder, buildErr := Builder(openrtb_ext.BidderDmx, config.Adapter{ - Endpoint: "https://dmx.districtm.io/b/v2"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - imp1 := openrtb2.Imp{ - ID: "imp1", - Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"), - Banner: &openrtb2.Banner{ - W: &width, - H: &height, - Format: []openrtb2.Format{ - {W: 300, H: 250}, - }, - }} - - inputRequest := openrtb2.BidRequest{ - Imp: []openrtb2.Imp{imp1, imp1, imp1}, - Site: &openrtb2.Site{ - Publisher: &openrtb2.Publisher{ - ID: "10007", - }, - }, - User: &openrtb2.User{Ext: json.RawMessage(`{ - "digitrust": { - "id": "11111111111", - "keyv": 4 - }}`)}, - ID: "1234", - } - - actualAdapterRequests, _ := bidder.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) - if len(actualAdapterRequests) != 1 { - t.Errorf("should have 1 request") - } -} - func TestUsersEids(t *testing.T) { var w, h int = 300, 250 @@ -725,53 +681,56 @@ func TestUsersEids(t *testing.T) { "rtiPartner": "TDID" } }] - },{ + }, + { "source": "pubcid.org", "uids": [{ - "id":"11111111" + "id": "11111111" }] }, - { + { "source": "id5-sync.com", "uids": [{ "id": "ID5-12345" }] - }, - { + }, + { "source": "parrable.com", "uids": [{ "id": "01.1563917337.test-eid" }] - },{ + }, + { "source": "identityLink", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "criteo", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "britepool.com", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "liveintent.com", "uids": [{ "id": "11111111" }] - },{ + }, + { "source": "netid.de", "uids": [{ "id": "11111111" }] - }], - "digitrust": { - "id": "11111111111", - "keyv": 4 - }}`)}, + }] + }`)}, ID: "1234", } diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 73f6a5d39ca..84d8596449c 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -96,12 +96,11 @@ type rubiconUserDataExt struct { } 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 { @@ -772,9 +771,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 diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 8f8d3fb1557..3674c872d73 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1039,11 +1039,7 @@ func TestOpenRTBRequest(t *testing.T) { 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" @@ -1117,10 +1113,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"}) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index a90670e53be..408cbb7979d 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -146,7 +146,6 @@ } ], "ext": { - "digitrust": null, "rp": { "target": { "iab": [ diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 079b9adb6d4..61164bd7272 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")), @@ -122,11 +121,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 +159,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 d8a7fa689b9..c9f2bbdb68f 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1124,13 +1124,9 @@ func (deps *endpointDeps) validateUser(user *openrtb2.User, aliases map[string]s } if user.Ext != nil { - // Creating ExtUser object to check if DigiTrust is valid + // Creating ExtUser object 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 { 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/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/exchange/exchange_test.go b/exchange/exchange_test.go index 2a61a4e8454..989e67078eb 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1787,7 +1787,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, @@ -1949,7 +1949,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", 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/utils.go b/exchange/utils.go index 3def0425819..0befeacdedd 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -332,7 +332,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 5a281f9a360..5a9fa187f62 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1788,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}`), @@ -1833,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", 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/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 { From 394bbadd0ffb7342e4b46fafedbc3a78f6f2443c Mon Sep 17 00:00:00 2001 From: Mani Gandham Date: Thu, 1 Jul 2021 10:21:22 -0700 Subject: [PATCH 029/140] IX: merge eventtrackers with imptrackers for native bid responses (#1900) --- adapters/ix/ix.go | 57 +++++++++- .../native-eventtrackers-compat-12.json | 104 ++++++++++++++++++ .../ix/ixtest/supplemental/bad-imp-id.json | 2 +- .../native-eventtrackers-empty.json | 104 ++++++++++++++++++ .../native-eventtrackers-missing.json | 104 ++++++++++++++++++ .../ixtest/supplemental/native-missing.json | 104 ++++++++++++++++++ 6 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json create mode 100644 adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json create mode 100644 adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json create mode 100644 adapters/ix/ixtest/supplemental/native-missing.json diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 5e10138f8f3..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 @@ -417,6 +420,28 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque } } + 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) + } + } + } + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ Bid: &bid, BidType: bidType, @@ -444,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/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" + } + ] + } + ] +} From 026e64ac0f437c4af5ca83d6e0b4a9a48d0dd451 Mon Sep 17 00:00:00 2001 From: armon823 <86739148+armon823@users.noreply.github.com> Date: Wed, 7 Jul 2021 09:13:06 -0700 Subject: [PATCH 030/140] Inmobi: user sync (#1911) --- adapters/inmobi/usersync.go | 12 ++++++++++++ config/config.go | 1 + static/bidder-info/inmobi.yaml | 2 +- usersync/usersyncers/syncer.go | 2 ++ usersync/usersyncers/syncer_test.go | 2 +- 5 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 adapters/inmobi/usersync.go 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/config/config.go b/config/config.go index f6fa9917178..34d941bc3ea 100644 --- a/config/config.go +++ b/config/config.go @@ -611,6 +611,7 @@ 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=194962&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") diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 9b11640f262..634c03481de 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -1,5 +1,5 @@ maintainer: - email: "prebid-support@inmobi.com" + email: "technology-irv@inmobi.com" gvlVendorID: 333 capabilities: app: diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 7472bf3a70d..53472018e30 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -49,6 +49,7 @@ 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" @@ -150,6 +151,7 @@ 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) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 81216b19199..35ceb1b70b5 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -58,6 +58,7 @@ func TestNewSyncerMap(t *testing.T) { 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, @@ -120,7 +121,6 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderBidsCube: true, openrtb_ext.BidderEpom: true, openrtb_ext.BidderDecenterAds: true, - openrtb_ext.BidderInMobi: true, openrtb_ext.BidderInteractiveoffers: true, openrtb_ext.BidderKayzen: true, openrtb_ext.BidderKidoz: true, From bc2c8a575c55a2f0279dbefb959688d4c953c67e Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Thu, 8 Jul 2021 18:23:54 +0300 Subject: [PATCH 031/140] Rubicon: Update segtax logic (#1909) Co-authored-by: Serhii Nahornyi --- adapters/rubicon/rubicon.go | 81 ++++++---- adapters/rubicon/rubicon_test.go | 28 ++++ .../rubicontest/exemplary/simple-video.json | 153 +++++++++++++++--- 3 files changed, 210 insertions(+), 52 deletions(-) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 84d8596449c..579af9839c7 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -91,8 +91,8 @@ type rubiconExtUserTpID struct { UID string `json:"uid"` } -type rubiconUserDataExt struct { - TaxonomyName string `json:"taxonomyname"` +type rubiconDataExt struct { + SegTax int `json:"segtax"` } type rubiconUserExt struct { @@ -104,7 +104,8 @@ type rubiconUserExt struct { } type rubiconSiteExtRP struct { - SiteID int `json:"site_id"` + SiteID int `json:"site_id"` + Target json.RawMessage `json:"target,omitempty"` } type rubiconSiteExt struct { @@ -755,12 +756,13 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada 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 @@ -845,19 +847,30 @@ 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}} + 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 @@ -904,41 +917,53 @@ func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, s return bidFloor, bidFloorCur } -func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error { +func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) { + var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes) + + 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) 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 3674c872d73..76904a42137 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1035,6 +1035,10 @@ func TestOpenRTBRequest(t *testing.T) { } }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, Device: &openrtb2.Device{ PxRatio: rubidata.devicePxRatio, }, @@ -1146,6 +1150,10 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) { "visitor": {"key2" : "val2"} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1195,6 +1203,10 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { } }`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1250,6 +1262,10 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { } }`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1283,6 +1299,10 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { "accountId": 7891 }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, User: &openrtb2.User{ Ext: json.RawMessage(`{"eids": [ { @@ -1391,6 +1411,10 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t "video": {"size_id": 1} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1436,6 +1460,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 408cbb7979d..11afdd50d2b 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" } ] } @@ -100,11 +149,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": [ { @@ -114,7 +221,7 @@ }, { "ext": { - "taxonomyname": "someValue" + "segtax": "someValue" }, "segment": [ { @@ -124,7 +231,17 @@ }, { "ext": { - "taxonomyname": "IaB" + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 }, "segment": [ { @@ -134,13 +251,13 @@ }, { "ext": { - "taxonomyname": [ - "wrong iab type" + "segtax": [ + 4 ] }, "segment": [ { - "id": "shouldNotBeCopied2" + "id": "shouldNotBeCopied3" } ] } @@ -158,18 +275,6 @@ }, "app": { "id": "1", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - }, "bundle": "com.wls.testwlsapplication" }, "imp": [ From e7f7b558951684f1fb75bc4c180506cdc91b5f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Fern=C3=A1ndez?= Date: Thu, 8 Jul 2021 19:29:46 +0200 Subject: [PATCH 032/140] New Adapter: Axonix (#1912) * New Axonix adapter * Changed endpoint * Rename adapter type * Leave in examplary only the basic test fixtures * PR comments --- adapters/axonix/axonix.go | 116 +++++++++++++ adapters/axonix/axonix_test.go | 30 ++++ .../exemplary/banner-and-video.json | 133 +++++++++++++++ .../exemplary/banner-video-native.json | 157 ++++++++++++++++++ .../axonixtest/exemplary/simple-banner.json | 105 ++++++++++++ .../axonixtest/exemplary/simple-video.json | 86 ++++++++++ .../axonix/axonixtest/params/race/banner.json | 3 + .../axonix/axonixtest/params/race/video.json | 3 + .../supplemental/bad-response-no-body.json | 57 +++++++ .../supplemental/status-bad-request.json | 58 +++++++ .../supplemental/status-no-content.json | 53 ++++++ .../supplemental/unexpected-status-code.json | 58 +++++++ .../supplemental/valid-extension.json | 86 ++++++++++ .../supplemental/valid-with-device.json | 93 +++++++++++ adapters/axonix/params_test.go | 59 +++++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_axonix.go | 5 + static/bidder-info/axonix.yaml | 15 ++ static/bidder-params/axonix.json | 14 ++ usersync/usersyncers/syncer_test.go | 1 + 22 files changed, 1137 insertions(+) create mode 100644 adapters/axonix/axonix.go create mode 100644 adapters/axonix/axonix_test.go create mode 100644 adapters/axonix/axonixtest/exemplary/banner-and-video.json create mode 100644 adapters/axonix/axonixtest/exemplary/banner-video-native.json create mode 100644 adapters/axonix/axonixtest/exemplary/simple-banner.json create mode 100644 adapters/axonix/axonixtest/exemplary/simple-video.json create mode 100644 adapters/axonix/axonixtest/params/race/banner.json create mode 100644 adapters/axonix/axonixtest/params/race/video.json create mode 100644 adapters/axonix/axonixtest/supplemental/bad-response-no-body.json create mode 100644 adapters/axonix/axonixtest/supplemental/status-bad-request.json create mode 100644 adapters/axonix/axonixtest/supplemental/status-no-content.json create mode 100644 adapters/axonix/axonixtest/supplemental/unexpected-status-code.json create mode 100644 adapters/axonix/axonixtest/supplemental/valid-extension.json create mode 100644 adapters/axonix/axonixtest/supplemental/valid-with-device.json create mode 100644 adapters/axonix/params_test.go create mode 100644 openrtb_ext/imp_axonix.go create mode 100644 static/bidder-info/axonix.yaml create mode 100644 static/bidder-params/axonix.json diff --git a/adapters/axonix/axonix.go b/adapters/axonix/axonix.go new file mode 100644 index 00000000000..d3016235319 --- /dev/null +++ b/adapters/axonix/axonix.go @@ -0,0 +1,116 @@ +package axonix + +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 Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + URI: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + + return nil, errors + } + + var axonixExt openrtb_ext.ExtImpAxonix + if err := json.Unmarshal(bidderExt.Bidder, &axonixExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + + return nil, errors + } + + thisURI := a.URI + if len(thisURI) == 0 { + thisURI = "https://openrtb-us-east-1.axonix.com/supply/prebid-server/" + axonixExt.SupplyId + } + + requestJSON, err := json.Marshal(request) + if err != nil { + errors = append(errors, err) + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json") + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: thisURI, + Body: requestJSON, + Headers: headers, + } + + 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.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 + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bid := bid + b := &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaType(bid.ImpID, request.Imp), + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, nil +} + +func getMediaType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impId { + if imp.Native != nil { + return openrtb_ext.BidTypeNative + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo + } + return openrtb_ext.BidTypeBanner + } + } + return openrtb_ext.BidTypeBanner +} diff --git a/adapters/axonix/axonix_test.go b/adapters/axonix/axonix_test.go new file mode 100644 index 00000000000..6c4a3eb34d6 --- /dev/null +++ b/adapters/axonix/axonix_test.go @@ -0,0 +1,30 @@ +package axonix + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func TestJsonSamplesWithConfiguredURI(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{ + Endpoint: "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "axonixtest", bidder) +} + +func TestJsonSamplesWithHardcodedURI(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "axonixtest", bidder) +} diff --git a/adapters/axonix/axonixtest/exemplary/banner-and-video.json b/adapters/axonix/axonixtest/exemplary/banner-and-video.json new file mode 100644 index 00000000000..1755cd0ef22 --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/banner-and-video.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] + +} diff --git a/adapters/axonix/axonixtest/exemplary/banner-video-native.json b/adapters/axonix/axonixtest/exemplary/banner-video-native.json new file mode 100644 index 00000000000..3944eb358b9 --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/banner-video-native.json @@ -0,0 +1,157 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "native-imp", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}" + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "native-imp", + "native": { + "ver": "1.1", + "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}" + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["axonix.com"], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] + +} diff --git a/adapters/axonix/axonixtest/exemplary/simple-banner.json b/adapters/axonix/axonixtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..581e59b9b9e --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/simple-banner.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "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": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + } + ] +} diff --git a/adapters/axonix/axonixtest/exemplary/simple-video.json b/adapters/axonix/axonixtest/exemplary/simple-video.json new file mode 100644 index 00000000000..c15d7876470 --- /dev/null +++ b/adapters/axonix/axonixtest/exemplary/simple-video.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "axonix", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "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/axonix/axonixtest/params/race/banner.json b/adapters/axonix/axonixtest/params/race/banner.json new file mode 100644 index 00000000000..7217c9c394f --- /dev/null +++ b/adapters/axonix/axonixtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" +} diff --git a/adapters/axonix/axonixtest/params/race/video.json b/adapters/axonix/axonixtest/params/race/video.json new file mode 100644 index 00000000000..7217c9c394f --- /dev/null +++ b/adapters/axonix/axonixtest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" +} diff --git a/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json b/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json new file mode 100644 index 00000000000..f89f40f122d --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json @@ -0,0 +1,57 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/status-bad-request.json b/adapters/axonix/axonixtest/supplemental/status-bad-request.json new file mode 100644 index 00000000000..d64a855e348 --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/status-bad-request.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.", + "comparison": "literal" + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/status-no-content.json b/adapters/axonix/axonixtest/supplemental/status-no-content.json new file mode 100644 index 00000000000..96dd899c1fb --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/status-no-content.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json b/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json new file mode 100644 index 00000000000..e7a7be7847e --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/axonix/axonixtest/supplemental/valid-extension.json b/adapters/axonix/axonixtest/supplemental/valid-extension.json new file mode 100644 index 00000000000..372f24d4f76 --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/valid-extension.json @@ -0,0 +1,86 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "axonix", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "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/axonix/axonixtest/supplemental/valid-with-device.json b/adapters/axonix/axonixtest/supplemental/valid-with-device.json new file mode 100644 index 00000000000..62f4ea06b5a --- /dev/null +++ b/adapters/axonix/axonixtest/supplemental/valid-with-device.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "3.0.0.0", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36" + }, + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "body": { + "id": "test-request-id", + "device": { + "ip": "3.0.0.0", + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36" + }, + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "axonix", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/axonix/params_test.go b/adapters/axonix/params_test.go new file mode 100644 index 00000000000..e9c0cc5b83e --- /dev/null +++ b/adapters/axonix/params_test.go @@ -0,0 +1,59 @@ +package axonix + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/axonix.json +// +// These also validate the format of the external API: request.imp[i].ext.axonix + +// TestValidParams makes sure that the Axonix 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.BidderAxonix, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Axonix params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the Axonix 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.BidderAxonix, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8"}`, + `{"supplyId": "test"}`, +} + +var invalidParams = []string{ + `{"supplyId": 100}`, + `{"supplyId": false}`, + `{"supplyId": true}`, + `{"supplyId": 123}`, + ``, + `null`, + `true`, + `9`, + `1.2`, + `[]`, + `{}`, +} diff --git a/config/config.go b/config/config.go index 34d941bc3ea..bc00a88fdb7 100644 --- a/config/config.go +++ b/config/config.go @@ -840,6 +840,7 @@ 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 17e0d4f54eb..9adc9ebc671 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -29,6 +29,7 @@ import ( "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" @@ -152,6 +153,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.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, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f2dd9815e8e..780b75f60b8 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -100,6 +100,7 @@ const ( BidderAppnexus BidderName = "appnexus" BidderAudienceNetwork BidderName = "audienceNetwork" BidderAvocet BidderName = "avocet" + BidderAxonix BidderName = "axonix" BidderBeachfront BidderName = "beachfront" BidderBeintoo BidderName = "beintoo" BidderBetween BidderName = "between" @@ -223,6 +224,7 @@ func CoreBidderNames() []BidderName { BidderAppnexus, BidderAudienceNetwork, BidderAvocet, + BidderAxonix, BidderBeachfront, BidderBeintoo, BidderBetween, 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/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-params/axonix.json b/static/bidder-params/axonix.json new file mode 100644 index 00000000000..7a3762ce5e2 --- /dev/null +++ b/static/bidder-params/axonix.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Axonix Adapter Params", + "description": "A schema which validates params accepted by the Axonix adapter", + "type": "object", + "properties": { + "supplyId": { + "type": "string", + "minLength": 1, + "description": "Unique supply identifier" + } + }, + "required": ["supplyId"] +} diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 35ceb1b70b5..59ed67cca0b 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -117,6 +117,7 @@ func TestNewSyncerMap(t *testing.T) { 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, From e87bec420e8521c278a38840e1d99dce464e8499 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Fri, 9 Jul 2021 11:44:09 -0400 Subject: [PATCH 033/140] Rubicon: Fix Nil Reference Panic (#1918) --- adapters/rubicon/rubicon.go | 17 +- .../supplemental/no-site-content-data.json | 293 ++++++++++++++++++ .../supplemental/no-site-content.json | 289 +++++++++++++++++ 3 files changed, 593 insertions(+), 6 deletions(-) create mode 100644 adapters/rubicon/rubicontest/supplemental/no-site-content-data.json create mode 100644 adapters/rubicon/rubicontest/supplemental/no-site-content.json diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 579af9839c7..84200431992 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -852,12 +852,14 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada if request.Site != nil { siteCopy := *request.Site siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} - target, err := updateExtWithIabAttribute(nil, siteCopy.Content.Data, []int{1, 2}) - if err != nil { - errs = append(errs, err) - continue + 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 } - siteExtRP.RP.Target = target siteCopy.Ext, err = json.Marshal(&siteExtRP) if err != nil { @@ -919,6 +921,9 @@ func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, s 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{}) @@ -938,7 +943,7 @@ func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, seg } func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string { - var segmentIdsToCopy = make([]string, 0) + var segmentIdsToCopy = make([]string, 0, len(data)) for _, dataRecord := range data { if dataRecord.Ext != nil { 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..f67788a3154 --- /dev/null +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json @@ -0,0 +1,293 @@ +{ + "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 + }, + "bidfloor": 1, + "bidfloorcur": "EuR", + "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 + }, + "bidfloor": 1.2, + "bidfloorcur": "USD", + "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..d3b8f8b7454 --- /dev/null +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content.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": { + }, + "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 + }, + "bidfloor": 1, + "bidfloorcur": "EuR", + "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 + }, + "bidfloor": 1.2, + "bidfloorcur": "USD", + "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" + } + ] + } + ] +} From aff5f70d8fee97d4641f8686c2a34d91a56385b4 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 13 Jul 2021 10:51:46 -0400 Subject: [PATCH 034/140] GDPR: host-level per-purpose vendor exceptions config (#1893) Co-authored-by: Scott Kay --- config/config.go | 67 ++++++++++++++-- config/config_test.go | 89 +++++++++++++++++++++ gdpr/gdpr.go | 15 ++++ gdpr/impl.go | 45 ++++++++--- gdpr/impl_test.go | 176 +++++++++++++++++++++++++++++++++++++++--- 5 files changed, 361 insertions(+), 31 deletions(-) diff --git a/config/config.go b/config/config.go index bc00a88fdb7..96e3c1f1e65 100644 --- a/config/config.go +++ b/config/config.go @@ -240,20 +240,30 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { // 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 PurposeOneTreatment `mapstructure:"purpose_one_treatment"` + 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 { +type TCF2Purpose struct { Enabled bool `mapstructure:"enabled"` + // 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 PurposeOneTreatment struct { +type TCF2PurposeOneTreatment struct { Enabled bool `mapstructure:"enabled"` AccessAllowed bool `mapstructure:"access_allowed"` } @@ -503,6 +513,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) @@ -957,9 +991,26 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.tcf2.enabled", true) v.SetDefault("gdpr.tcf2.purpose1.enabled", true) v.SetDefault("gdpr.tcf2.purpose2.enabled", true) + v.SetDefault("gdpr.tcf2.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.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.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", diff --git a/config/config_test.go b/config/config_test.go index fa9dcdc5195..c33a345c473 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -147,6 +147,30 @@ gdpr: host_vendor_id: 15 default_value: "1" non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] + tcf2: + purpose1: + vendor_exceptions: ["foo1a", "foo1b"] + purpose2: + enabled: false + vendor_exceptions: ["foo2"] + purpose3: + vendor_exceptions: ["foo3"] + purpose4: + vendor_exceptions: ["foo4"] + purpose5: + vendor_exceptions: ["foo5"] + purpose6: + vendor_exceptions: ["foo6"] + purpose7: + vendor_exceptions: ["foo7"] + purpose8: + vendor_exceptions: ["foo8"] + purpose9: + vendor_exceptions: ["foo9"] + purpose10: + vendor_exceptions: ["foo10"] + special_purpose1: + vendor_exceptions: ["fooSP1"] ccpa: enforce: true lmt: @@ -378,6 +402,71 @@ 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 + 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, + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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") diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 47d20a50899..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" @@ -44,9 +45,23 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ 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, gdprDefaultValue: gdprDefaultValue, + purposeConfigs: purposeConfigs, vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)}, diff --git a/gdpr/impl.go b/gdpr/impl.go index 17a1a893e1c..ac0bfefdba4 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -30,6 +30,7 @@ const ( type permissionsImpl struct { 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) } @@ -41,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) { @@ -53,7 +54,8 @@ 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 @@ -80,9 +82,9 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, } if id, ok := p.vendorIDs[bidder]; ok { - return p.allowActivities(ctx, id, consent, weakVendorEnforcement) + return p.allowActivities(ctx, id, bidder, consent, weakVendorEnforcement) } else if weakVendorEnforcement { - return p.allowActivities(ctx, 0, consent, weakVendorEnforcement) + return p.allowActivities(ctx, 0, bidder, consent, weakVendorEnforcement) } return p.defaultVendorPermissions() @@ -104,7 +106,7 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { 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 @@ -127,10 +129,10 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen err := fmt.Errorf("Unable to access TCF2 parsed consent") return false, err } - return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, false), nil + return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, vendorException, false), nil } -func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err 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 @@ -156,17 +158,20 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, } if p.cfg.TCF2.SpecialPurpose1.Enabled { - passGeo = consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) + vendorException := p.isSpecialPurposeVendorException(bidder) + passGeo = vendorException || (consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement)) } else { passGeo = true } if p.cfg.TCF2.Purpose2.Enabled { - allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), weakVendorEnforcement) + 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(consentMeta, vendor, vendorID, consentconstants.Purpose(i), weakVendorEnforcement) { + vendorException := p.isVendorException(consentconstants.Purpose(i), bidder) + if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), vendorException, weakVendorEnforcement) { passID = true break } @@ -175,11 +180,25 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, return } +func (p *permissionsImpl) isVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (vendorException bool) { + if _, ok := p.purposeConfigs[purpose].VendorExceptionMap[bidder]; ok { + vendorException = true + } + return +} + +func (p *permissionsImpl) isSpecialPurposeVendorException(bidder openrtb_ext.BidderName) (vendorException bool) { + if _, ok := p.cfg.TCF2.SpecialPurpose1.VendorExceptionMap[bidder]; ok { + vendorException = true + } + return +} + const pubRestrictNotAllowed = 0 const pubRestrictRequireConsent = 1 const pubRestrictRequireLegitInterest = 2 -func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool { +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 } @@ -187,6 +206,10 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api. return false } + if vendorException { + return true + } + purposeAllowed := consent.PurposeAllowed(purpose) && (weakVendorEnforcement || (vendor.Purpose(purpose) && consent.VendorConsent(vendorID))) legitInterest := consent.PurposeLITransparency(purpose) && (weakVendorEnforcement || (vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID))) diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index cde485467b3..93d23c1acf6 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" @@ -64,7 +65,7 @@ func TestAllowedSyncs(t *testing.T) { cfg: config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ - Purpose1: config.PurposeDetail{ + Purpose1: config.TCF2Purpose{ Enabled: true, }, }, @@ -104,7 +105,7 @@ func TestProhibitedPurposes(t *testing.T) { cfg: config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ - Purpose1: config.PurposeDetail{ + Purpose1: config.TCF2Purpose{ Enabled: true, }, }, @@ -143,7 +144,7 @@ func TestProhibitedVendors(t *testing.T) { cfg: config.GDPR{ HostVendorID: 2, TCF2: config.TCF2{ - Purpose1: config.PurposeDetail{ + Purpose1: config.TCF2Purpose{ Enabled: true, }, }, @@ -287,7 +288,7 @@ func TestAllowActivities(t *testing.T) { NonStandardPublisherMap: map[string]struct{}{"appNexusAppID": {}}, TCF2: config.TCF2{ Enabled: true, - Purpose2: config.PurposeDetail{ + Purpose2: config.TCF2Purpose{ Enabled: true, }, }, @@ -360,10 +361,10 @@ var gdprConfig = 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}, + Purpose1: config.TCF2Purpose{Enabled: true}, + Purpose2: config.TCF2Purpose{Enabled: true}, + Purpose7: config.TCF2Purpose{Enabled: true}, + SpecialPurpose1: config.TCF2Purpose{Enabled: true}, }, } @@ -796,10 +797,10 @@ func TestAllowActivitiesBidRequests(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, - Purpose1: config.PurposeDetail{Enabled: true}, - Purpose2: config.PurposeDetail{Enabled: td.purpose2Enabled}, - Purpose7: config.PurposeDetail{Enabled: true}, - SpecialPurpose1: config.PurposeDetail{Enabled: true}, + Purpose1: config.TCF2Purpose{Enabled: true}, + Purpose2: config.TCF2Purpose{Enabled: td.purpose2Enabled}, + Purpose7: config.TCF2Purpose{Enabled: true}, + SpecialPurpose1: config.TCF2Purpose{Enabled: true}, }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ @@ -837,3 +838,154 @@ func TestTCF1Consent(t *testing.T) { 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) + } +} From e37b1a672537175c77726c0ccae59ead9c5f15cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Wed, 14 Jul 2021 22:06:29 +0200 Subject: [PATCH 035/140] Criteo - Fix fields mapping error when building bid from bidder response (#1917) --- adapters/criteo/criteo.go | 4 +- .../exemplary/simple-banner-cookie-uid.json | 230 ++++----- .../exemplary/simple-banner-inapp.json | 220 ++++----- .../exemplary/simple-banner-uid.json | 264 +++++----- ...ccpa-with-consent-simple-banner-inapp.json | 229 +++++---- .../ccpa-with-consent-simple-banner-uid.json | 277 ++++++----- ...gdpr-with-consent-simple-banner-inapp.json | 251 +++++----- .../gdpr-with-consent-simple-banner-uid.json | 283 ++++++----- .../supplemental/multislots-alt-case.json | 12 +- .../multislots-simple-banner-inapp.json | 425 ++++++++-------- .../multislots-simple-banner-uid.json | 465 +++++++++--------- ...-direct-size-and-formats-not-included.json | 264 +++++----- ...e-banner-with-direct-size-and-formats.json | 264 +++++----- .../simple-banner-with-direct-size.json | 252 +++++----- .../supplemental/simple-banner-with-ipv6.json | 252 +++++----- adapters/criteo/models.go | 20 +- 16 files changed, 1853 insertions(+), 1859 deletions(-) diff --git a/adapters/criteo/criteo.go b/adapters/criteo/criteo.go index 9f6ca4d74ec..de5568b9b9b 100644 --- a/adapters/criteo/criteo.go +++ b/adapters/criteo/criteo.go @@ -86,13 +86,13 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest for i := 0; i < len(bidResponse.Slots); i++ { bidderResponse.Bids[i] = &adapters.TypedBid{ Bid: &openrtb2.Bid{ - ID: bidResponse.Slots[i].ID, + ID: bidResponse.Slots[i].ArbitrageID, ImpID: bidResponse.Slots[i].ImpID, Price: bidResponse.Slots[i].CPM, AdM: bidResponse.Slots[i].Creative, W: bidResponse.Slots[i].Width, H: bidResponse.Slots[i].Height, - CrID: bidResponse.Slots[i].CreativeID, + CrID: bidResponse.Slots[i].CreativeCode, }, BidType: openrtb_ext.BidTypeBanner, } diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json index cb152f47c41..cb1179501ce 100755 --- a/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json +++ b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json @@ -1,115 +1,115 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "buyeruid": "criteo-user-id" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"], - "Cookie": ["uid=criteo-user-id"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "cookieuid": "criteo-user-id", - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "buyeruid": "criteo-user-id" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"], + "Cookie": ["uid=criteo-user-id"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "cookieuid": "criteo-user-id", + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json index 0ec1b7f87a5..3dec57ffc46 100755 --- a/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json +++ b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json @@ -1,110 +1,110 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "app": { - "bundle": "test.app.bundle" - }, - "device": { - "ifa": "test-ifa-123456", - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "bundleid": "test.app.bundle", - "networkid": 78910 - }, - "user": { - "deviceid": "test-ifa-123456", - "deviceos": "android", - "deviceidtype": "gaid", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json index 2499790bf5a..ea453deee2c 100755 --- a/adapters/criteo/criteotest/exemplary/simple-banner-uid.json +++ b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json @@ -1,132 +1,132 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json index b047fa183d3..febc3e7d16a 100755 --- a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json +++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json @@ -1,115 +1,114 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "app": { - "bundle": "test.app.bundle" - }, - "device": { - "ifa": "test-ifa-123456", - "ip": "91.199.242.236", - "ua": "random user agent", - "os": "android" - }, - "regs": { - "ext": { - "us_privacy": "1YYY" - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "bundleid": "test.app.bundle", - "networkid": 78910 - }, - "user": { - "deviceid": "test-ifa-123456", - "deviceos": "android", - "deviceidtype": "gaid", - "uspIab": "1YYY", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "ip": "91.199.242.236", + "ua": "random user agent", + "os": "android" + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YYY", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json index f839624af1e..4046b641785 100755 --- a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json +++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json @@ -1,139 +1,138 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "regs": { - "ext": { - "us_privacy": "1YYY" - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid", - "uspIab": "1YYY" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "regs": { + "ext": { + "us_privacy": "1YYY" + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid", + "uspIab": "1YYY" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json index 416053dbc67..5bb5f98bec9 100755 --- a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json +++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json @@ -1,126 +1,125 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "app": { - "bundle": "test.app.bundle" - }, - "device": { - "ifa": "test-ifa-123456", - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "consent": "iabconsentstringwithconsent" - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "bundleid": "test.app.bundle", - "networkid": 78910 - }, - "user": { - "deviceid": "test-ifa-123456", - "deviceos": "android", - "deviceidtype": "gaid", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "gdprconsent": { - "consentdata": "iabconsentstringwithconsent", - "gdprapplies": true - }, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ifa": "test-ifa-123456", + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "consent": "iabconsentstringwithconsent" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": { + "consentdata": "iabconsentstringwithconsent", + "gdprapplies": true + }, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json index e8374616db3..ea64fb0c122 100755 --- a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json +++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json @@ -1,142 +1,141 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "consent": "iabconsentstringwithconsent", - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "regs": { - "ext": { - "gdpr": 1 - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": { - "consentdata": "iabconsentstringwithconsent", - "gdprapplies": true - }, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "consent": "iabconsentstringwithconsent", + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": { + "consentdata": "iabconsentstringwithconsent", + "gdprapplies": true + }, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/multislots-alt-case.json b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json index beb855e3f2b..cb7022c0404 100644 --- a/adapters/criteo/criteotest/supplemental/multislots-alt-case.json +++ b/adapters/criteo/criteotest/supplemental/multislots-alt-case.json @@ -146,7 +146,7 @@ "body": { "slots": [ { - "id": "test-slot-id-1", + "arbitrageid": "test-slot-id-1", "impid": "test-imp-id-1", "zoneid": 123456, "networkid": 78910, @@ -154,11 +154,11 @@ "currency": "USD", "width": 300, "height": 250, - "creativeid": "creative-123", + "creativecode": "creative-123", "creative": "" }, { - "id": "test-slot-id-2", + "arbitrageid": "test-slot-id-2", "impid": "test-imp-id-2", "zoneid": 7891011, "networkid": 78910, @@ -166,11 +166,11 @@ "currency": "USD", "width": 320, "height": 50, - "creativeid": "creative-123", + "creativecode": "creative-123", "creative": "" }, { - "id": "test-slot-id-3", + "arbitrageid": "test-slot-id-3", "impid": "test-imp-id-3", "zoneid": 121314, "networkid": 78910, @@ -178,7 +178,7 @@ "currency": "USD", "width": 300, "height": 600, - "creativeid": "creative-123", + "creativecode": "creative-123", "creative": "" } ] diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json index 75f4118eb93..2986601342b 100755 --- a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json +++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json @@ -1,213 +1,212 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "app": { - "bundle": "test.app.bundle" - }, - "device": { - "ip": "91.199.242.236", - "ua": "random user agent", - "ifa": "test-ifa-123456", - "os": "android" - }, - "imp": [ - { - "id": "test-imp-id-1", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 7891011, - "networkid": 78910 - } - } - }, - { - "id": "test-imp-id-3", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 121314, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "bundleid": "test.app.bundle", - "networkid": 78910 - }, - "user": { - "deviceid": "test-ifa-123456", - "deviceos": "android", - "deviceidtype": "gaid", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-1", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - }, - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-2", - "zoneid": 7891011, - "networkid": 78910, - "sizes": [ - "300x250" - ] - }, - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-3", - "zoneid": 121314, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id-1", - "impid": "test-imp-id-1", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - }, - { - "id": "test-slot-id-2", - "impid": "test-imp-id-2", - "zoneid": 7891011, - "networkid": 78910, - "cpm": 0.2, - "currency": "USD", - "width": 320, - "height": 50, - "creativeid": "creative-123", - "creative": "" - }, - { - "id": "test-slot-id-3", - "impid": "test-imp-id-3", - "zoneid": 121314, - "networkid": 78910, - "cpm": 0.3, - "currency": "USD", - "width": 300, - "height": 600, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id-1", - "impid": "test-imp-id-1", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-slot-id-2", - "impid": "test-imp-id-2", - "price": 0.2, - "crid": "creative-123", - "adm": "", - "w": 320, - "h": 50 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-slot-id-3", - "impid": "test-imp-id-3", - "price": 0.3, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 600 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "test.app.bundle" + }, + "device": { + "ip": "91.199.242.236", + "ua": "random user agent", + "ifa": "test-ifa-123456", + "os": "android" + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 7891011, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 121314, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "bundleid": "test.app.bundle", + "networkid": 78910 + }, + "user": { + "deviceid": "test-ifa-123456", + "deviceos": "android", + "deviceidtype": "gaid", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id-1", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + }, + { + "arbitrageid": "test-slot-id-2", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "cpm": 0.2, + "currency": "USD", + "width": 320, + "height": 50, + "creativecode": "creative-123", + "creative": "" + }, + { + "arbitrageid": "test-slot-id-3", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "cpm": 0.3, + "currency": "USD", + "width": 300, + "height": 600, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "price": 0.2, + "crid": "creative-123", + "adm": "", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "price": 0.3, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json index e5de75e04c0..913f19b7710 100755 --- a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json +++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json @@ -1,233 +1,232 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id-1", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - }, - { - "id": "test-imp-id-2", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 7891011, - "networkid": 78910 - } - } - }, - { - "id": "test-imp-id-3", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 121314, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": [ - "91.199.242.236" - ], - "User-Agent": [ - "random user agent" - ] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-1", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - }, - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-2", - "zoneid": 7891011, - "networkid": 78910, - "sizes": [ - "300x250" - ] - }, - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id-3", - "zoneid": 121314, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "slots": [ - { - "id": "test-slot-id-1", - "impid": "test-imp-id-1", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - }, - { - "id": "test-slot-id-2", - "impid": "test-imp-id-2", - "zoneid": 7891011, - "networkid": 78910, - "cpm": 0.2, - "currency": "USD", - "width": 320, - "height": 50, - "creativeid": "creative-123", - "creative": "" - }, - { - "id": "test-slot-id-3", - "impid": "test-imp-id-3", - "zoneid": 121314, - "networkid": 78910, - "cpm": 0.3, - "currency": "USD", - "width": 300, - "height": 600, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id-1", - "impid": "test-imp-id-1", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-slot-id-2", - "impid": "test-imp-id-2", - "price": 0.2, - "crid": "creative-123", - "adm": "", - "w": 320, - "h": 50 - }, - "type": "banner" - }, - { - "bid": { - "id": "test-slot-id-3", - "impid": "test-imp-id-3", - "price": 0.3, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 600 - }, - "type": "banner" - } - ] - } - ] - } - \ No newline at end of file +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 7891011, + "networkid": 78910 + } + } + }, + { + "id": "test-imp-id-3", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 121314, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": [ + "91.199.242.236" + ], + "User-Agent": [ + "random user agent" + ] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "sizes": [ + "300x250" + ] + }, + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "slots": [ + { + "arbitrageid": "test-slot-id-1", + "impid": "test-imp-id-1", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + }, + { + "arbitrageid": "test-slot-id-2", + "impid": "test-imp-id-2", + "zoneid": 7891011, + "networkid": 78910, + "cpm": 0.2, + "currency": "USD", + "width": 320, + "height": 50, + "creativecode": "creative-123", + "creative": "" + }, + { + "arbitrageid": "test-slot-id-3", + "impid": "test-imp-id-3", + "zoneid": 121314, + "networkid": 78910, + "cpm": 0.3, + "currency": "USD", + "width": 300, + "height": 600, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id-1", + "impid": "test-imp-id-1", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-2", + "impid": "test-imp-id-2", + "price": 0.2, + "crid": "creative-123", + "adm": "", + "w": 320, + "h": 50 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-slot-id-3", + "impid": "test-imp-id-3", + "price": 0.3, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 600 + }, + "type": "banner" + } + ] + } + ] + } diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json index a0cc53d00f3..8f421f2b71e 100755 --- a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json @@ -1,132 +1,132 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250, - "format": [ - { - "w": 250, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "250x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 250, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "250x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json index 016c16a866f..cf73d3074cc 100755 --- a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json @@ -1,132 +1,132 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250, - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250, + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json index 68e45da9fb3..ae965477b34 100755 --- a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json @@ -1,126 +1,126 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ip": "91.199.242.236", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["91.199.242.236"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ip": "91.199.242.236", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ip": "91.199.242.236", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["91.199.242.236"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ip": "91.199.242.236", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json index e248db9bc30..0aa021d2d6d 100755 --- a/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json +++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json @@ -1,126 +1,126 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "site": { - "id": "site-id", - "page": "criteo.com" - }, - "device": { - "os": "android", - "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", - "ua": "random user agent" - }, - "user": { - "ext": { - "eids": [{ - "source": "criteo.com", - "uids": [{ - "id": "criteo-eid" - }] - }] - } - }, - "imp": [ - { - "id": "test-imp-id", - "banner": { - "w": 300, - "h": 250 - }, - "ext": { - "bidder": { - "zoneid": 123456, - "networkid": 78910 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bidder.criteo.com/cdb?profileId=230", - "headers": { - "X-Forwarded-For": ["fd36:ce97:0fa1:dec0:0000:0000:0000:0000"], - "User-Agent": ["random user agent"] - }, - "body": { - "id": "test-request-id", - "publisher": { - "siteid": "site-id", - "url": "criteo.com", - "networkid": 78910 - }, - "user": { - "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", - "ua": "random user agent", - "deviceos": "android", - "deviceidtype": "gaid" - }, - "gdprconsent": {}, - "slots": [ - { - "slotid": "00000000-0000-0000-00000000", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "sizes": [ - "300x250" - ] - } - ], - "eids": [ - { - "source": "criteo.com", - "uids": [ - { - "id": "criteo-eid" - } - ] - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "slots": [ - { - "id": "test-slot-id", - "impid": "test-imp-id", - "zoneid": 123456, - "networkid": 78910, - "cpm": 0.1, - "currency": "USD", - "width": 300, - "height": 250, - "creativeid": "creative-123", - "creative": "" - } - ] - } - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-slot-id", - "impid": "test-imp-id", - "price": 0.1, - "crid": "creative-123", - "adm": "", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} - +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "id": "site-id", + "page": "criteo.com" + }, + "device": { + "os": "android", + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent" + }, + "user": { + "ext": { + "eids": [{ + "source": "criteo.com", + "uids": [{ + "id": "criteo-eid" + }] + }] + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zoneid": 123456, + "networkid": 78910 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bidder.criteo.com/cdb?profileId=230", + "headers": { + "X-Forwarded-For": ["fd36:ce97:0fa1:dec0:0000:0000:0000:0000"], + "User-Agent": ["random user agent"] + }, + "body": { + "id": "test-request-id", + "publisher": { + "siteid": "site-id", + "url": "criteo.com", + "networkid": 78910 + }, + "user": { + "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000", + "ua": "random user agent", + "deviceos": "android", + "deviceidtype": "gaid" + }, + "gdprconsent": {}, + "slots": [ + { + "slotid": "00000000-0000-0000-00000000", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "sizes": [ + "300x250" + ] + } + ], + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "criteo-eid" + } + ] + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "slots": [ + { + "arbitrageid": "test-slot-id", + "impid": "test-imp-id", + "zoneid": 123456, + "networkid": 78910, + "cpm": 0.1, + "currency": "USD", + "width": 300, + "height": 250, + "creativecode": "creative-123", + "creative": "" + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-slot-id", + "impid": "test-imp-id", + "price": 0.1, + "crid": "creative-123", + "adm": "", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/criteo/models.go b/adapters/criteo/models.go index 1ae51c12f8b..0365c0e62a6 100644 --- a/adapters/criteo/models.go +++ b/adapters/criteo/models.go @@ -286,14 +286,14 @@ func newCriteoResponseFromBytes(bytes []byte) (criteoResponse, error) { } type criteoResponseSlot struct { - ID string `json:"id,omitempty"` - ImpID string `json:"impid,omitempty"` - ZoneID int64 `json:"zoneid,omitempty"` - NetworkID int64 `json:"networkid,omitempty"` - CPM float64 `json:"cpm,omitempty"` - Currency string `json:"currency,omitempty"` - Width int64 `json:"width,omitempty"` - Height int64 `json:"height,omitempty"` - Creative string `json:"creative,omitempty"` - CreativeID string `json:"creativeid,omitempty"` + ArbitrageID string `json:"arbitrageid,omitempty"` + ImpID string `json:"impid,omitempty"` + ZoneID int64 `json:"zoneid,omitempty"` + NetworkID int64 `json:"networkid,omitempty"` + CPM float64 `json:"cpm,omitempty"` + Currency string `json:"currency,omitempty"` + Width int64 `json:"width,omitempty"` + Height int64 `json:"height,omitempty"` + Creative string `json:"creative,omitempty"` + CreativeCode string `json:"creativecode,omitempty"` } From 150be5300d94a75710d69dabe3fb1708f60db184 Mon Sep 17 00:00:00 2001 From: el-chuck Date: Thu, 15 Jul 2021 00:22:48 +0200 Subject: [PATCH 036/140] Smaato: Rework multi imp support and add adpod support (#1902) --- adapters/smaato/image.go | 42 +- adapters/smaato/image_test.go | 59 +- adapters/smaato/params_test.go | 2 + adapters/smaato/richmedia.go | 41 +- adapters/smaato/richmedia_test.go | 53 +- adapters/smaato/smaato.go | 543 +++++++++++------ adapters/smaato/smaato_test.go | 93 +++ .../exemplary/multiple-impressions.json | 354 +++++++++++ .../exemplary/simple-banner-app.json | 5 +- .../simple-banner-richMedia-app.json | 5 +- .../exemplary/simple-banner-richMedia.json | 5 +- .../smaatotest/exemplary/simple-banner.json | 5 +- .../smaatotest/exemplary/video-app.json | 5 +- .../smaato/smaatotest/exemplary/video.json | 5 +- .../smaatotest/params/{ => race}/banner.json | 0 .../smaato/smaatotest/params/race/video.json | 4 + .../supplemental/adtype-header-response.json | 194 ++++++ .../supplemental/bad-adm-response.json | 5 +- .../bad-adtype-header-response.json | 174 ++++++ .../bad-expires-header-response.json | 194 ++++++ .../smaatotest/supplemental/bad-ext-req.json | 54 -- .../bad-imp-banner-format-req.json | 61 -- .../bad-imp-banner-format-request.json | 28 + .../supplemental/bad-media-type-request.json | 27 + .../supplemental/bad-site-ext-request.json | 34 ++ ...400.json => bad-status-code-response.json} | 4 +- ...eq.json => bad-user-ext-data-request.json} | 14 +- .../supplemental/bad-user-ext-request.json | 36 ++ .../supplemental/banner-w-and-h.json | 173 ++++++ .../supplemental/expires-header-response.json | 194 ++++++ ...ta-req.json => no-adspace-id-request.json} | 27 +- .../supplemental/no-app-site-request.json | 30 + ...tus-code-204.json => no-bid-response.json} | 6 +- ...info.json => no-consent-info-request.json} | 5 +- .../{no-imp-req.json => no-imp-request.json} | 7 +- .../supplemental/no-publisher-id-request.json | 29 + .../outdated-expires-header-response.json | 193 ++++++ .../smaatotest/video/multiple-adpods.json | 555 ++++++++++++++++++ .../smaato/smaatotest/video/single-adpod.json | 293 +++++++++ .../bad-adbreak-id-request.json | 90 +++ .../videosupplemental/bad-adm-response.json | 276 +++++++++ .../bad-bid-ext-response.json | 274 +++++++++ .../bad-media-type-request.json | 37 ++ .../bad-publisher-id-request.json | 90 +++ openrtb_ext/imp_smaato.go | 5 +- static/bidder-params/smaato.json | 20 +- 46 files changed, 3926 insertions(+), 424 deletions(-) create mode 100644 adapters/smaato/smaatotest/exemplary/multiple-impressions.json rename adapters/smaato/smaatotest/params/{ => race}/banner.json (100%) create mode 100644 adapters/smaato/smaatotest/params/race/video.json create mode 100644 adapters/smaato/smaatotest/supplemental/adtype-header-response.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json delete mode 100644 adapters/smaato/smaatotest/supplemental/bad-ext-req.json delete mode 100644 adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-media-type-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json rename adapters/smaato/smaatotest/supplemental/{status-code-400.json => bad-status-code-response.json} (97%) rename adapters/smaato/smaatotest/supplemental/{bad-user-ext-req.json => bad-user-ext-data-request.json} (81%) create mode 100644 adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/banner-w-and-h.json create mode 100644 adapters/smaato/smaatotest/supplemental/expires-header-response.json rename adapters/smaato/smaatotest/supplemental/{bad-user-ext-data-req.json => no-adspace-id-request.json} (65%) create mode 100644 adapters/smaato/smaatotest/supplemental/no-app-site-request.json rename adapters/smaato/smaatotest/supplemental/{status-code-204.json => no-bid-response.json} (96%) rename adapters/smaato/smaatotest/supplemental/{no-consent-info.json => no-consent-info-request.json} (98%) rename adapters/smaato/smaatotest/supplemental/{no-imp-req.json => no-imp-request.json} (59%) create mode 100644 adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json create mode 100644 adapters/smaato/smaatotest/video/multiple-adpods.json create mode 100644 adapters/smaato/smaatotest/video/single-adpod.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json 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..c50efffc994 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.3" 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,224 @@ 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) } +} - if request.Site != nil { - siteCopy := *request.Site - siteCopy.Publisher = &openrtb2.Publisher{ID: publisherID} +// 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 request.Site.Ext != nil { - var siteExt siteExt - err := json.Unmarshal([]byte(request.Site.Ext), &siteExt) + 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 { - errs = append(errs, err) - return nil, errs + errors = append(errors, err) + continue } - siteCopy.Keywords = siteExt.Data.Keywords - siteCopy.Ext = nil + + 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, + }) } - request.Site = &siteCopy } + return bidResponse, errors +} - if request.App != nil { - appCopy := *request.App - appCopy.Publisher = &openrtb2.Publisher{ID: publisherID} +func (adapter *adapter) makeIndividualRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) { + imps := request.Imp - request.App = &appCopy - } + requests := make([]*adapters.RequestData, 0, len(imps)) + errors := make([]error, 0, len(imps)) - if request.User != nil && request.User.Ext != nil { - var userExt userExt - var userExtRaw map[string]json.RawMessage + for _, imp := range imps { + request.Imp = []openrtb2.Imp{imp} + if err := prepareIndividualRequest(request); err != nil { + errors = append(errors, err) + continue + } - rawExtErr := json.Unmarshal(request.User.Ext, &userExtRaw) - if rawExtErr != nil { - errs = append(errs, rawExtErr) - return nil, errs + requestData, err := adapter.makeRequest(request) + if err != nil { + errors = append(errors, err) + continue } - userExtErr := json.Unmarshal([]byte(request.User.Ext), &userExt) - if userExtErr != nil { - errs = append(errs, userExtErr) - return nil, errs + requests = append(requests, requestData) + } + + return requests, errors +} + +func (adapter *adapter) makePodRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) { + pods, orderedKeys, errors := groupImpressionsByPod(request.Imp) + requests := make([]*adapters.RequestData, 0, len(pods)) + + 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..e86fea8eb04 --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json @@ -0,0 +1,354 @@ +{ + "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" + } + } + }, + { + "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 + } + }, + "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", + "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.3" + } + } + }, + "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", + "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.3" + } + } + }, + "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/simple-banner-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json index 8194f568c28..2752fa6e6c7 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.3" } } }, @@ -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..bc3a3c28c87 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.3" } } }, @@ -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..7f81d39cd81 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.3" } } }, @@ -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..f83e347a684 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -180,7 +180,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..317003f52b3 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.3" } } }, @@ -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..85699129180 100644 --- a/adapters/smaato/smaatotest/exemplary/video.json +++ b/adapters/smaato/smaatotest/exemplary/video.json @@ -122,7 +122,7 @@ } }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.3" } } }, @@ -177,7 +177,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..59302b6de59 --- /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.3" + } + } + }, + "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..885c077e624 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.3" } } }, @@ -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..cfb23bbef85 --- /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.3" + } + } + }, + "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..8e41524493d --- /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.3" + } + } + }, + "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..a087594325e 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.3" } } }, @@ -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..9e57c380d27 --- /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.3" + } + } + }, + "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..90a78e626cf --- /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.3" + } + } + }, + "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..c8821d2d944 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.3" } } }, @@ -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..72f8a2e3b9d 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.3" } } }, @@ -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..c65e0824338 --- /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.3" + } + } + }, + "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..c909dc15f25 --- /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.3" + } + } + }, + "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.3" + } + } + }, + "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..b5bc09d495c --- /dev/null +++ b/adapters/smaato/smaatotest/video/single-adpod.json @@ -0,0 +1,293 @@ +{ + "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.3" + } + } + }, + "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..b13906ce066 --- /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.3" + } + } + }, + "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..2e0556ff15e --- /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.3" + } + } + }, + "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/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/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 From d58e15fdff451074b6eb809c5a52c53333a0b68c Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Thu, 15 Jul 2021 04:55:35 -0700 Subject: [PATCH 037/140] Allowed $0.00 price bids if there are deals (#1910) --- exchange/bidder_validate_bids.go | 7 +++- exchange/bidder_validate_bids_test.go | 60 +++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index aec0948ddde..d58c9ebba50 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -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 06973b837c2..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, false) - assert.Len(t, seatBid.bids, 3) + 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, 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, false) - assert.Len(t, seatBid.bids, 2) - assert.Len(t, errs, 3) + assert.Len(t, seatBid.bids, 3) + assert.Len(t, errs, 5) } func TestCurrencyBids(t *testing.T) { From 09291bbd3736ed7d3e289b2fc8aeb753280f0a7a Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Thu, 15 Jul 2021 14:26:08 -0400 Subject: [PATCH 038/140] GDPR: host-level per-purpose enforce vendor signals config (#1921) * Add GDPR host-level per-purpose enforce vendor signals config * Update config defaults test with TCF2 object compare --- config/config.go | 13 +- config/config_test.go | 97 +++++++++++- gdpr/impl.go | 36 ++++- gdpr/impl_test.go | 349 +++++++++++++++++++++++++----------------- 4 files changed, 353 insertions(+), 142 deletions(-) diff --git a/config/config.go b/config/config.go index 96e3c1f1e65..b64bab0006f 100644 --- a/config/config.go +++ b/config/config.go @@ -257,7 +257,8 @@ type TCF2 struct { // Making a purpose struct so purpose specific details can be added later. type TCF2Purpose struct { - Enabled bool `mapstructure:"enabled"` + 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{} @@ -999,6 +1000,16 @@ func SetupViper(v *viper.Viper, filename string) { 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{}) diff --git a/config/config_test.go b/config/config_test.go index c33a345c473..a87d65af359 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -138,8 +138,81 @@ func TestDefaults(t *testing.T) { 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) - cmpBools(t, "gdpr.tcf2.purpose_one_treatment.enabled", true, cfg.GDPR.TCF2.PurposeOneTreatment.Enabled) - cmpBools(t, "gdpr.tcf2.purpose_one_treatment.access_allowed", true, cfg.GDPR.TCF2.PurposeOneTreatment.AccessAllowed) + + //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(` @@ -149,25 +222,35 @@ gdpr: 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"] @@ -407,51 +490,61 @@ func TestFullConfig(t *testing.T) { 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"): {}}, }, diff --git a/gdpr/impl.go b/gdpr/impl.go index ac0bfefdba4..5f7e3e73fe2 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -210,8 +210,8 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api. return true } - purposeAllowed := consent.PurposeAllowed(purpose) && (weakVendorEnforcement || (vendor.Purpose(purpose) && consent.VendorConsent(vendorID))) - legitInterest := consent.PurposeLITransparency(purpose) && (weakVendorEnforcement || (vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID))) + 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 @@ -224,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 { diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 93d23c1acf6..f7d90f3673b 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -66,7 +66,8 @@ func TestAllowedSyncs(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + Enabled: true, + EnforceVendors: true, }, }, }, @@ -79,6 +80,9 @@ func TestAllowedSyncs(t *testing.T) { }), }, } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + } allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2AndPurpose1Consent) assertNilErr(t, err) @@ -145,7 +149,8 @@ func TestProhibitedVendors(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + Enabled: true, + EnforceVendors: true, }, }, }, @@ -158,6 +163,9 @@ func TestProhibitedVendors(t *testing.T) { }), }, } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + } allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, purpose1NoVendorConsent) assertNilErr(t, err) @@ -289,7 +297,8 @@ func TestAllowActivities(t *testing.T) { TCF2: config.TCF2{ Enabled: true, Purpose2: config.TCF2Purpose{ - Enabled: true, + Enabled: true, + EnforceVendors: true, }, }, }, @@ -302,6 +311,9 @@ func TestAllowActivities(t *testing.T) { }), }, } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2, + } for _, tt := range tests { perms.cfg.DefaultValue = tt.gdprDefaultValue @@ -357,15 +369,39 @@ func buildVendorList34() vendorList { } } -var gdprConfig = config.GDPR{ - HostVendorID: 2, - TCF2: config.TCF2{ - Enabled: true, - Purpose1: config.TCF2Purpose{Enabled: true}, - Purpose2: config.TCF2Purpose{Enabled: true}, - Purpose7: config.TCF2Purpose{Enabled: true}, - SpecialPurpose1: config.TCF2Purpose{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 testDef struct { @@ -380,20 +416,20 @@ type testDef struct { func TestAllowActivitiesGeoAndID(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - 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){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - 74: parseVendorListDataV2(t, vendorListData), - }), - }, + + 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 : full consents to purposes and vendors 2, 6, 8 @@ -464,19 +500,19 @@ func TestAllowActivitiesGeoAndID(t *testing.T) { func TestAllowActivitiesWhitelist(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - 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){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, + + 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": {}} _, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false) @@ -487,18 +523,17 @@ func TestAllowActivitiesWhitelist(t *testing.T) { func TestAllowActivitiesPubRestrict(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - 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){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 15: parseVendorListDataV2(t, vendorListData), - }), - }, + + 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, @@ -537,18 +572,17 @@ func TestAllowActivitiesPubRestrict(t *testing.T) { func TestAllowSync(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - 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){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, + + 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), + }), } // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consensts to purposes and vendors 2, 6, 8 @@ -565,20 +599,19 @@ func TestProhibitedPurposeSync(t *testing.T) { vendorList34 := buildVendorList34() vendorList34.Vendors["8"].Purposes = []int{7} vendorListData := MarshalVendorList(vendorList34) - perms := permissionsImpl{ - cfg: gdprConfig, - 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){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } + + 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 : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") @@ -592,21 +625,20 @@ func TestProhibitedPurposeSync(t *testing.T) { func TestProhibitedVendorSync(t *testing.T) { vendorListData := MarshalVendorList(buildVendorList34()) - perms := permissionsImpl{ - cfg: gdprConfig, - 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){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } + + 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 : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") @@ -751,67 +783,110 @@ 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 - bidder openrtb_ext.BidderName - consent string - allowBid bool - passGeo bool - passID bool - weakVendorEnforcement bool + 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, - bidder: openrtb_ext.BidderPubmatic, - consent: purpose2ConsentWithoutVendorConsent, - allowBid: false, - passGeo: false, - passID: false, + 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 disabled, user consents to p2 but not vendor, vendor consents to p2", - purpose2Enabled: false, - bidder: openrtb_ext.BidderPubmatic, - consent: purpose2ConsentWithoutVendorConsent, - allowBid: true, - 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, user consents to p2 and vendor, vendor consents to p2", - purpose2Enabled: true, - bidder: openrtb_ext.BidderPubmatic, - consent: purpose2AndVendorConsent, - 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 := permissionsImpl{ - cfg: config.GDPR{ - HostVendorID: 2, - TCF2: config.TCF2{ - Enabled: true, - Purpose1: config.TCF2Purpose{Enabled: true}, - Purpose2: config.TCF2Purpose{Enabled: td.purpose2Enabled}, - Purpose7: config.TCF2Purpose{Enabled: true}, - SpecialPurpose1: config.TCF2Purpose{Enabled: true}, - }, - }, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderPubmatic: 6, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, + + 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) From aee52e33a5b531659728a474f81121da40e276cd Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 15 Jul 2021 11:43:57 -0700 Subject: [PATCH 039/140] Fix for fetcher warning at server startup (#1914) Co-authored-by: Veronika Solovei --- stored_requests/config/config.go | 3 ++ stored_requests/config/config_test.go | 78 +++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 5 deletions(-) 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) + } } } From ac3c657339a0cfbf0beecadb0f68cb571f8d9d00 Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Tue, 20 Jul 2021 17:14:55 -0400 Subject: [PATCH 040/140] Request Wrapper first pass (#1784) --- endpoints/openrtb2/amp_auction.go | 2 +- endpoints/openrtb2/amp_auction_test.go | 1 + endpoints/openrtb2/auction.go | 242 +++--- endpoints/openrtb2/auction_test.go | 21 +- endpoints/openrtb2/interstitial.go | 18 +- endpoints/openrtb2/interstitial_test.go | 3 +- .../invalid-whole/invalid-source.json | 2 +- .../invalid-whole/regs-ext-gdpr-string.json | 2 +- .../invalid-whole/regs-ext-malformed.json | 2 +- .../invalid-whole/user-ext-consent-int.json | 2 +- .../user-gdpr-consent-invalid.json | 2 +- endpoints/openrtb2/video_auction.go | 2 +- endpoints/openrtb2/video_auction_test.go | 4 +- exchange/utils.go | 30 +- exchange/utils_test.go | 8 +- go.sum | 2 + openrtb_ext/device.go | 8 +- openrtb_ext/request_wrapper.go | 787 ++++++++++++++++++ openrtb_ext/request_wrapper_test.go | 22 + privacy/ccpa/consentwriter.go | 19 +- privacy/ccpa/consentwriter_test.go | 50 ++ privacy/ccpa/policy.go | 184 +--- privacy/ccpa/policy_test.go | 123 ++- 23 files changed, 1185 insertions(+), 351 deletions(-) create mode 100644 openrtb_ext/request_wrapper.go create mode 100644 openrtb_ext/request_wrapper_test.go diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 862311daa6e..6b4fa6890b8 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -310,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 61164bd7272..2ce1180d50b 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -66,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()) } diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index c9f2bbdb68f..a6b3479a36c 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -174,10 +174,17 @@ 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, + BidRequest: req.BidRequest, Account: *account, UserSyncs: usersyncs, RequestType: labels.RType, @@ -188,7 +195,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http } response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) - ao.Request = req + ao.Request = req.BidRequest ao.Response = response ao.Account = account if err != nil { @@ -227,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. @@ -258,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 { @@ -296,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\"")} @@ -318,34 +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(bidExt); err != nil { + if err := validateSChains(reqPrebid.SChains); err != nil { return []error{err} } - if err := deps.validateEidPermissions(bidExt, aliases); err != nil { + if err := deps.validateEidPermissions(reqPrebid.Data, aliases); err != nil { return []error{err} } - if err := validateCustomRates(bidExt.Prebid.CurrencyConversions); err != nil { + if err := validateCustomRates(reqPrebid.CurrencyConversions); err != nil { return []error{err} } } @@ -354,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) } @@ -374,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) } @@ -437,8 +451,8 @@ 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 } @@ -466,13 +480,13 @@ func validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) er return nil } -func (deps *endpointDeps) validateEidPermissions(req *openrtb_ext.ExtRequest, aliases map[string]string) error { - if req == nil || req.Prebid.Data == 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(req.Prebid.Data.EidPermissions)) - for i, eid := range req.Prebid.Data.EidPermissions { + 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) } @@ -1045,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 +func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error { + if _, err := req.GetRequestExt(); err != nil { + return fmt.Errorf("request.ext is invalid: %v", err) } - var tmpExt openrtb_ext.ExtRequest - if err := json.Unmarshal(ext, &tmpExt); err != nil { - return nil, fmt.Errorf("request.ext is invalid: %v", err) - } - return &tmpExt, nil + return nil } func (deps *endpointDeps) validateAliases(aliases map[string]string) error { @@ -1073,120 +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 user.Ext != nil { - // Creating ExtUser object - var userExt openrtb_ext.ExtUser - if err := json.Unmarshal(user.Ext, &userExt); err == nil { - // 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) - } - } + 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") + } + } + + 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 bcdac13dc06..4835dd92943 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1587,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}) @@ -1633,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) { @@ -1684,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}) @@ -1731,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") } @@ -1773,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}) @@ -1992,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}) @@ -2007,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{}, @@ -2096,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) } } 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/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/video_auction.go b/endpoints/openrtb2/video_auction.go index 0af3ba512bb..227f6c4a943 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -241,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 diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 9f0859a32cd..5452d6c2c39 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1087,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", @@ -1132,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/exchange/utils.go b/exchange/utils.go index 0befeacdedd..120152466a8 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 } } } @@ -178,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 } @@ -217,9 +216,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) diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 5a9fa187f62..1d13928b59c 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1908,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) @@ -1934,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) @@ -1947,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) @@ -1960,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/go.sum b/go.sum index 4cb5863dd41..df2bfb9b459 100644 --- a/go.sum +++ b/go.sum @@ -80,9 +80,11 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI 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= 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/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/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) } From 5ec40b9262a8c7ee75c397a5a088e1fc2ec7012e Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Wed, 21 Jul 2021 21:55:11 +0300 Subject: [PATCH 041/140] Rubicon: Use currency conversion function (#1924) Co-authored-by: Serhii Nahornyi --- adapters/bidder.go | 6 +- adapters/rubicon/rubicon.go | 28 ++-- adapters/rubicon/rubicon_test.go | 131 ++++++++++++++---- .../rubicontest/exemplary/simple-video.json | 4 - .../supplemental/no-site-content-data.json | 4 - .../supplemental/no-site-content.json | 4 - 6 files changed, 123 insertions(+), 54 deletions(-) diff --git a/adapters/bidder.go b/adapters/bidder.go index b7bde4bc55d..dbe56f8eb91 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -139,12 +139,12 @@ func (r *RequestData) SetBasicAuth(username string, password string) { type ExtraRequestInfo struct { PbsEntryPoint metrics.RequestType GlobalPrivacyControlHeader string - currencyConversions currency.Conversions + CurrencyConversions currency.Conversions } func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { return ExtraRequestInfo{ - currencyConversions: c, + CurrencyConversions: c, } } @@ -153,7 +153,7 @@ func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { // - ConversionNotFoundError if the conversion mapping is unknown to Prebid Server // and not provided in the bid request. func (r ExtraRequestInfo) ConvertCurrency(value float64, from, to string) (float64, error) { - if rate, err := r.currencyConversions.GetRate(from, to); err == nil { + if rate, err := r.CurrencyConversions.GetRate(from, to); err == nil { return value * rate, nil } else { return 0, err diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 84200431992..e9627916cc6 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -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,9 +749,19 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } - resolvedBidFloor, resolvedBidFloorCur := resolveBidFloorAttributes(thisImp.BidFloor, thisImp.BidFloorCur) - thisImp.BidFloorCur = resolvedBidFloorCur - thisImp.BidFloor = resolvedBidFloor + 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 @@ -908,15 +917,12 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada return requestData, errs } -// Will be replaced after https://github.com/prebid/prebid-server/issues/1482 resolution -func resolveBidFloorAttributes(bidFloor float64, bidFloorCur string) (float64, string) { - if bidFloor > 0 { - if strings.ToUpper(bidFloorCur) == "EUR" { - return bidFloor * 1.2, "USD" - } +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, bidFloorCur + return bidFloor, nil } func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) { diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 76904a42137..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,52 +574,125 @@ func TestResolveVideoSizeId(t *testing.T) { } } -func TestResolveBidFloorAttributes(t *testing.T) { +func TestOpenRTBRequestWithDifferentBidFloorAttributes(t *testing.T) { testScenarios := []struct { - bidFloor float64 - bidFloorCur string - expectedBidFloor float64 - expectedBidFloorCur string + bidFloor float64 + bidFloorCur string + setMock func(m *mock.Mock) + expectedBidFloor float64 + expectedBidCur string + expectedErrors []error }{ { - bidFloor: 1, - bidFloorCur: "EUR", - expectedBidFloor: 1.2, - expectedBidFloorCur: "USD", + 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: "Eur", - expectedBidFloor: 1.2, - expectedBidFloorCur: "USD", + bidFloor: 1, + bidFloorCur: "USD", + setMock: func(m *mock.Mock) {}, + expectedBidFloor: 1, + expectedBidCur: "USD", + expectedErrors: nil, }, { - bidFloor: 0, - bidFloorCur: "EUR", - expectedBidFloor: 0, - expectedBidFloorCur: "EUR", + 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: -1, - bidFloorCur: "EUR", - expectedBidFloor: -1, - expectedBidFloorCur: "EUR", + bidFloor: 0, + bidFloorCur: "", + setMock: func(m *mock.Mock) {}, + expectedBidFloor: 0, + expectedBidCur: "", + expectedErrors: nil, }, { - bidFloor: 1, - bidFloorCur: "USD", - expectedBidFloor: 1, - expectedBidFloorCur: "USD", + bidFloor: -1, + bidFloorCur: "CZK", + setMock: func(m *mock.Mock) {}, + expectedBidFloor: -1, + expectedBidCur: "CZK", + expectedErrors: nil, }, } for _, scenario := range testScenarios { - bidFloor, bidFloorCur := resolveBidFloorAttributes(scenario.bidFloor, scenario.bidFloorCur) - assert.Equal(t, scenario.expectedBidFloor, bidFloor) - assert.Equal(t, scenario.expectedBidFloorCur, bidFloorCur) + 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) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 11afdd50d2b..1daffe9b386 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -120,8 +120,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1, - "bidfloorcur": "EuR", "ext": { "bidder": { "video": { @@ -298,8 +296,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1.2, - "bidfloorcur": "USD", "ext": { "rp": { "track": { diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json index f67788a3154..0be214da4bc 100644 --- a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json @@ -85,8 +85,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1, - "bidfloorcur": "EuR", "ext": { "bidder": { "video": { @@ -221,8 +219,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1.2, - "bidfloorcur": "USD", "ext": { "rp": { "track": { diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content.json b/adapters/rubicon/rubicontest/supplemental/no-site-content.json index d3b8f8b7454..2e830a2dd00 100644 --- a/adapters/rubicon/rubicontest/supplemental/no-site-content.json +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content.json @@ -83,8 +83,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1, - "bidfloorcur": "EuR", "ext": { "bidder": { "video": { @@ -217,8 +215,6 @@ "w": 1024, "h": 576 }, - "bidfloor": 1.2, - "bidfloorcur": "USD", "ext": { "rp": { "track": { From ba1fe79ae1719c87a791329920808dabaa57b295 Mon Sep 17 00:00:00 2001 From: jizeyopera <70930512+jizeyopera@users.noreply.github.com> Date: Thu, 22 Jul 2021 06:35:46 +0800 Subject: [PATCH 042/140] New Adapter: operaads (#1916) --- adapters/operaads/operaads.go | 215 ++++++++++++++++++ adapters/operaads/operaads_test.go | 20 ++ .../operaadstest/exemplary/native.json | 137 +++++++++++ .../operaadstest/exemplary/simple-banner.json | 156 +++++++++++++ .../operaadstest/exemplary/video.json | 173 ++++++++++++++ .../operaadstest/supplemental/badrequest.json | 84 +++++++ .../supplemental/banner-size-miss.json | 50 ++++ .../supplemental/miss-native.json | 137 +++++++++++ .../supplemental/missing-device.json | 33 +++ .../operaadstest/supplemental/nocontent.json | 82 +++++++ .../supplemental/unexcept-statuscode.json | 84 +++++++ adapters/operaads/params_test.go | 54 +++++ adapters/operaads/usersync.go | 11 + adapters/operaads/usersync_test.go | 33 +++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_operaads.go | 7 + static/bidder-info/operaads.yaml | 14 ++ static/bidder-params/operaads.json | 28 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 22 files changed, 1327 insertions(+) create mode 100644 adapters/operaads/operaads.go create mode 100644 adapters/operaads/operaads_test.go create mode 100644 adapters/operaads/operaadstest/exemplary/native.json create mode 100644 adapters/operaads/operaadstest/exemplary/simple-banner.json create mode 100644 adapters/operaads/operaadstest/exemplary/video.json create mode 100644 adapters/operaads/operaadstest/supplemental/badrequest.json create mode 100644 adapters/operaads/operaadstest/supplemental/banner-size-miss.json create mode 100644 adapters/operaads/operaadstest/supplemental/miss-native.json create mode 100644 adapters/operaads/operaadstest/supplemental/missing-device.json create mode 100644 adapters/operaads/operaadstest/supplemental/nocontent.json create mode 100644 adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json create mode 100644 adapters/operaads/params_test.go create mode 100644 adapters/operaads/usersync.go create mode 100644 adapters/operaads/usersync_test.go create mode 100644 openrtb_ext/imp_operaads.go create mode 100644 static/bidder-info/operaads.yaml create mode 100644 static/bidder-params/operaads.json 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/config/config.go b/config/config.go index b64bab0006f..b8ff3bd6116 100644 --- a/config/config.go +++ b/config/config.go @@ -663,6 +663,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") @@ -930,6 +931,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) diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 9adc9ebc671..1d91fadd96d 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -80,6 +80,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" @@ -206,6 +207,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.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, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 780b75f60b8..8a589bd1a74 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -153,6 +153,7 @@ const ( BidderNoBid BidderName = "nobid" BidderOneTag BidderName = "onetag" BidderOpenx BidderName = "openx" + BidderOperaads BidderName = "operaads" BidderOrbidder BidderName = "orbidder" BidderOutbrain BidderName = "outbrain" BidderPangle BidderName = "pangle" @@ -277,6 +278,7 @@ func CoreBidderNames() []BidderName { BidderNoBid, BidderOneTag, BidderOpenx, + BidderOperaads, BidderOrbidder, BidderOutbrain, BidderPangle, 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/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-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/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 53472018e30..674fc136527 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -1,6 +1,7 @@ package usersyncers import ( + "github.com/prebid/prebid-server/adapters/operaads" "strings" "text/template" @@ -169,6 +170,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) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 59ed67cca0b..8a2a3e5d2e1 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -75,6 +75,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, From 0aa427ebccffa042ab9242dacbfe32a90f5f9afa Mon Sep 17 00:00:00 2001 From: Mansi Nahar Date: Thu, 22 Jul 2021 15:29:35 -0400 Subject: [PATCH 043/140] Fix Beachfront data race condition (#1915) Co-authored-by: Jim Naumann --- adapters/beachfront/beachfront.go | 5 +- .../exemplary/adm-video-app.json | 124 ++++++++++++++++++ .../adm-video-app-alphanum-bundle.json | 123 +++++++++++++++++ .../adm-video-app-malformed-bundle.json | 124 ++++++++++++++++++ 4 files changed, 374 insertions(+), 2 deletions(-) create mode 100644 adapters/beachfront/beachfronttest/exemplary/adm-video-app.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 6eba9923e64..7f4658d442c 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -449,8 +449,9 @@ func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, [ var chunks = strings.Split(strings.Trim(bfReqs[i].Request.App.Bundle, "_"), ".") if len(chunks) > 1 { - bfReqs[i].Request.App.Domain = - fmt.Sprintf("%s.%s", chunks[len(chunks)-(len(chunks)-1)], chunks[0]) + appCopy := *bfReqs[i].Request.App + appCopy.Domain = fmt.Sprintf("%s.%s", chunks[len(chunks)-(len(chunks)-1)], chunks[0]) + bfReqs[i].Request.App = &appCopy } } } diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json b/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json new file mode 100644 index 00000000000..0734a212c61 --- /dev/null +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "app": { + "bundle": "com.domain.some" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 0 + } + ], + "app": { + "bundle": "com.domain.some", + "domain": "domain.com" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 1, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json new file mode 100644 index 00000000000..0e3a32d5437 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json @@ -0,0 +1,123 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "app": { + "bundle": "id1234567890" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 0 + } + ], + "app": { + "bundle": "id1234567890" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 1, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json new file mode 100644 index 00000000000..8327426ca0f --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "app": { + "bundle": "some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 0 + } + ], + "app": { + "bundle": "some.domain.us/some/page.html", + "domain": "domain.some" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 1, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} From a68f7e249c00d0b6e810c6cfbb2fab011d744cb3 Mon Sep 17 00:00:00 2001 From: Eddy Pechuzal <46331062+epechuzal@users.noreply.github.com> Date: Thu, 22 Jul 2021 12:49:36 -0700 Subject: [PATCH 044/140] Sharethrough: Add support for GPID (#1925) --- adapters/sharethrough/butler.go | 10 ++++++++++ adapters/sharethrough/butler_test.go | 4 +++- openrtb_ext/imp_sharethrough.go | 15 ++++++++++----- static/bidder-params/sharethrough.json | 10 ++++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) 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/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/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"] From cf8b2ffd651828a36a88d311d22e9ecf9e68de83 Mon Sep 17 00:00:00 2001 From: avolokha <84977155+avolokha@users.noreply.github.com> Date: Tue, 27 Jul 2021 20:37:20 +0300 Subject: [PATCH 045/140] Admixer: Fix for bid floor issue#1787 (#1872) --- adapters/admixer/admixer.go | 7 +- .../exemplary/optional-params.json | 133 ++++++++++++++++++ adapters/admixer/params_test.go | 4 + static/bidder-params/admixer.json | 4 +- 4 files changed, 144 insertions(+), 4 deletions(-) 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/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 From a3e4d4827ac0493e356c8c87ec6c92cbd2862560 Mon Sep 17 00:00:00 2001 From: Daniel Lawrence Date: Wed, 28 Jul 2021 07:48:52 -0700 Subject: [PATCH 046/140] InMobi: adding native support (#1928) --- adapters/inmobi/inmobi.go | 3 + .../exemplary/simple-app-native.json | 105 ++++++++++++++++++ static/bidder-info/inmobi.yaml | 1 + 3 files changed, 109 insertions(+) create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-app-native.json 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-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/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 634c03481de..d62a2c9239d 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -6,6 +6,7 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner From c01c993fb5f0c86475f9650fbea6369a7cb3780e Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Thu, 29 Jul 2021 16:47:37 +0200 Subject: [PATCH 047/140] Tappx: new bidder params (#1931) Co-authored-by: Albert Grandes --- adapters/tappx/params_test.go | 15 ++ adapters/tappx/tappx.go | 31 ++++- adapters/tappx/tappx_test.go | 2 +- .../single-banner-impression-extra.json | 130 ++++++++++++++++++ ...ngle-banner-impression-future-feature.json | 9 +- .../exemplary/single-banner-impression.json | 7 +- .../exemplary/single-banner-site.json | 7 +- .../exemplary/single-video-impression.json | 7 +- .../exemplary/single-video-site.json | 7 +- .../tappxtest/supplemental/204status.json | 7 +- .../tappxtest/supplemental/bidfloor.json | 7 +- .../supplemental/http-err-status.json | 7 +- .../supplemental/http-err-status2.json | 7 +- openrtb_ext/imp_tappx.go | 11 +- static/bidder-params/tappx.json | 18 +++ 15 files changed, 255 insertions(+), 17 deletions(-) create mode 100644 adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json 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/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/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"] From 4a782647b842c1023fbcbe7f348c4b689a99fcc8 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 29 Jul 2021 11:00:46 -0400 Subject: [PATCH 048/140] Fix CVE-2020-35381 (#1942) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7d10a5c9bca..6193e08c47c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf 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 diff --git a/go.sum b/go.sum index df2bfb9b459..a4cab742c34 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,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= From 2e9b8971803df098d5ecaf702c24a1150bd3aca8 Mon Sep 17 00:00:00 2001 From: el-chuck Date: Thu, 29 Jul 2021 17:42:26 +0200 Subject: [PATCH 049/140] Smaato: Split multiple media types (#1930) Co-authored-by: Bernhard Pickenbrock --- adapters/smaato/smaato.go | 49 ++- .../exemplary/multiple-impressions.json | 8 +- .../exemplary/multiple-media-types.json | 348 ++++++++++++++++++ .../exemplary/simple-banner-app.json | 2 +- .../simple-banner-richMedia-app.json | 2 +- .../exemplary/simple-banner-richMedia.json | 2 +- .../smaatotest/exemplary/simple-banner.json | 4 +- .../smaatotest/exemplary/video-app.json | 2 +- .../smaato/smaatotest/exemplary/video.json | 4 +- .../supplemental/adtype-header-response.json | 2 +- .../supplemental/bad-adm-response.json | 2 +- .../bad-adtype-header-response.json | 2 +- .../bad-expires-header-response.json | 2 +- .../bad-status-code-response.json | 2 +- .../supplemental/banner-w-and-h.json | 2 +- .../supplemental/expires-header-response.json | 2 +- .../supplemental/no-bid-response.json | 2 +- .../supplemental/no-consent-info-request.json | 2 +- .../outdated-expires-header-response.json | 2 +- .../smaatotest/video/multiple-adpods.json | 4 +- .../smaato/smaatotest/video/single-adpod.json | 6 +- .../videosupplemental/bad-adm-response.json | 2 +- .../bad-bid-ext-response.json | 2 +- 23 files changed, 422 insertions(+), 33 deletions(-) create mode 100644 adapters/smaato/smaatotest/exemplary/multiple-media-types.json diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index c50efffc994..c84dd356a59 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -17,7 +17,7 @@ import ( "github.com/prebid/prebid-server/util/timeutil" ) -const clientVersion = "prebid_server_0.3" +const clientVersion = "prebid_server_0.4" type adMarkupType string @@ -160,24 +160,53 @@ func (adapter *adapter) makeIndividualRequests(request *openrtb2.BidRequest) ([] errors := make([]error, 0, len(imps)) for _, imp := range imps { - request.Imp = []openrtb2.Imp{imp} - if err := prepareIndividualRequest(request); err != nil { - errors = append(errors, err) - continue - } - - requestData, err := adapter.makeRequest(request) + impsByMediaType, err := splitImpressionsByMediaType(&imp) if err != nil { errors = append(errors, err) continue } - requests = append(requests, requestData) + 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 { + errors = append(errors, err) + continue + } + + requests = append(requests, requestData) + } } return requests, errors } +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."} + } + + imps := make([]openrtb2.Imp, 0, 2) + + 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)) @@ -436,7 +465,7 @@ func setImpForAdspace(imp *openrtb2.Imp) error { return nil } - return &errortypes.BadInput{Message: "Invalid MediaType. Smaato only supports Banner and Video."} + return nil } func setImpForAdBreak(imps []openrtb2.Imp) error { diff --git a/adapters/smaato/smaatotest/exemplary/multiple-impressions.json b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json index e86fea8eb04..c30a9a6a39e 100644 --- a/adapters/smaato/smaatotest/exemplary/multiple-impressions.json +++ b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json @@ -31,6 +31,7 @@ } ] }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -65,6 +66,7 @@ "rewarded": 0 } }, + "bidfloor": 0.00456, "ext": { "bidder": { "publisherId": "1100042526", @@ -114,6 +116,7 @@ { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "tagid": "130563103", + "bidfloor": 0.00123, "banner": { "h": 50, "w": 320, @@ -159,7 +162,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, @@ -208,6 +211,7 @@ { "id": "postbid_iframe", "tagid": "130563104", + "bidfloor": 0.00456, "video": { "w": 1024, "h": 768, @@ -264,7 +268,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, 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 2752fa6e6c7..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.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json index bc3a3c28c87..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.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json index 7f81d39cd81..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.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json index f83e347a684..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.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/video-app.json b/adapters/smaato/smaatotest/exemplary/video-app.json index 317003f52b3..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.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json index 85699129180..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.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json index 59302b6de59..14b966f9bdd 100644 --- a/adapters/smaato/smaatotest/supplemental/adtype-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json index 885c077e624..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.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json index cfb23bbef85..b066dc1b6cb 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json index 8e41524493d..065b639509e 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json index a087594325e..bfe3dbe2a2d 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json index 9e57c380d27..1d59e96d634 100644 --- a/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json +++ b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json @@ -107,7 +107,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/expires-header-response.json b/adapters/smaato/smaatotest/supplemental/expires-header-response.json index 90a78e626cf..be057419177 100644 --- a/adapters/smaato/smaatotest/supplemental/expires-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/expires-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/no-bid-response.json b/adapters/smaato/smaatotest/supplemental/no-bid-response.json index c8821d2d944..4f674f2a34d 100644 --- a/adapters/smaato/smaatotest/supplemental/no-bid-response.json +++ b/adapters/smaato/smaatotest/supplemental/no-bid-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json index 72f8a2e3b9d..b33fea6e7e1 100644 --- a/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json +++ b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json @@ -72,7 +72,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json index c65e0824338..abab70facbd 100644 --- a/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json +++ b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/video/multiple-adpods.json b/adapters/smaato/smaatotest/video/multiple-adpods.json index c909dc15f25..e5023d87b28 100644 --- a/adapters/smaato/smaatotest/video/multiple-adpods.json +++ b/adapters/smaato/smaatotest/video/multiple-adpods.json @@ -234,7 +234,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, @@ -386,7 +386,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/video/single-adpod.json b/adapters/smaato/smaatotest/video/single-adpod.json index b5bc09d495c..a5f0c0590f5 100644 --- a/adapters/smaato/smaatotest/video/single-adpod.json +++ b/adapters/smaato/smaatotest/video/single-adpod.json @@ -24,6 +24,7 @@ 7 ] }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -53,6 +54,7 @@ 7 ] }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -101,6 +103,7 @@ { "id": "1_1", "tagid": "400000001", + "bidfloor": 0.00123, "video": { "w": 1024, "h": 768, @@ -129,6 +132,7 @@ { "id": "1_2", "tagid": "400000001", + "bidfloor": 0.00123, "video": { "w": 1024, "h": 768, @@ -176,7 +180,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json index b13906ce066..08803d1894e 100644 --- a/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json +++ b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json @@ -176,7 +176,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json index 2e0556ff15e..75e288362c0 100644 --- a/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json +++ b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json @@ -176,7 +176,7 @@ } }, "ext": { - "client": "prebid_server_0.3" + "client": "prebid_server_0.4" } } }, From 88589d5c39baf1ed41bf22abd86f5ea341ae992c Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 29 Jul 2021 19:00:17 +0200 Subject: [PATCH 050/140] New adapter: Adagio (#1907) --- adapters/adagio/adagio.go | 139 +++++++++++ adapters/adagio/adagio_test.go | 75 ++++++ .../adagiotest/exemplary/banner-web.json | 155 ++++++++++++ .../adagiotest/exemplary/multi-format.json | 165 +++++++++++++ .../adagiotest/exemplary/multi-imp.json | 222 ++++++++++++++++++ .../adagiotest/exemplary/native-web.json | 151 ++++++++++++ .../adagiotest/exemplary/video-web.json | 175 ++++++++++++++ .../adagio/adagiotest/params/race/banner.json | 5 + .../adagio/adagiotest/params/race/native.json | 5 + .../adagio/adagiotest/params/race/video.json | 5 + .../response-miss-ext-bid-type.json | 130 ++++++++++ .../adagiotest/supplemental/status-204.json | 62 +++++ .../adagiotest/supplemental/status-400.json | 67 ++++++ .../adagiotest/supplemental/status-401.json | 67 ++++++ .../adagiotest/supplemental/status-403.json | 67 ++++++ .../adagiotest/supplemental/status-500.json | 68 ++++++ .../adagiotest/supplemental/status-503.json | 67 ++++++ .../adagiotest/supplemental/status-504.json | 67 ++++++ adapters/adagio/params_test.go | 57 +++++ adapters/adagio/usersync.go | 12 + adapters/adagio/usersync_test.go | 34 +++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/adagio.yaml | 12 + static/bidder-params/adagio.json | 94 ++++++++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 28 files changed, 1910 insertions(+) create mode 100644 adapters/adagio/adagio.go create mode 100644 adapters/adagio/adagio_test.go create mode 100644 adapters/adagio/adagiotest/exemplary/banner-web.json create mode 100644 adapters/adagio/adagiotest/exemplary/multi-format.json create mode 100644 adapters/adagio/adagiotest/exemplary/multi-imp.json create mode 100644 adapters/adagio/adagiotest/exemplary/native-web.json create mode 100644 adapters/adagio/adagiotest/exemplary/video-web.json create mode 100644 adapters/adagio/adagiotest/params/race/banner.json create mode 100644 adapters/adagio/adagiotest/params/race/native.json create mode 100644 adapters/adagio/adagiotest/params/race/video.json create mode 100644 adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-204.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-400.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-401.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-403.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-500.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-503.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-504.json create mode 100644 adapters/adagio/params_test.go create mode 100644 adapters/adagio/usersync.go create mode 100644 adapters/adagio/usersync_test.go create mode 100644 static/bidder-info/adagio.yaml create mode 100644 static/bidder-params/adagio.json 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/config/config.go b/config/config.go index b8ff3bd6116..6e8b661a13b 100644 --- a/config/config.go +++ b/config/config.go @@ -602,6 +602,7 @@ 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. @@ -849,6 +850,7 @@ 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 1d91fadd96d..e1dbc3509b2 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -4,6 +4,7 @@ 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" @@ -129,6 +130,7 @@ 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.BidderAdagio: adagio.Builder, openrtb_ext.BidderAdf: adf.Builder, openrtb_ext.BidderAdform: adform.Builder, openrtb_ext.BidderAdgeneration: adgeneration.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 8a589bd1a74..2a08fd19eb2 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -75,6 +75,7 @@ func IsBidderNameReserved(name string) bool { const ( Bidder33Across BidderName = "33across" BidderAcuityAds BidderName = "acuityads" + BidderAdagio BidderName = "adagio" BidderAdf BidderName = "adf" BidderAdform BidderName = "adform" BidderAdgeneration BidderName = "adgeneration" @@ -200,6 +201,7 @@ func CoreBidderNames() []BidderName { return []BidderName{ Bidder33Across, BidderAcuityAds, + BidderAdagio, BidderAdf, BidderAdform, BidderAdgeneration, 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-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/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 674fc136527..8275869f5b2 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -8,6 +8,7 @@ import ( "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" @@ -111,6 +112,7 @@ 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) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 8a2a3e5d2e1..342d2ae81b9 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -16,6 +16,7 @@ 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, From bab487e8a54b37dca75c5f86694170e60d638ef3 Mon Sep 17 00:00:00 2001 From: Joshua Gross <820727+grossjo@users.noreply.github.com> Date: Thu, 29 Jul 2021 14:12:02 -0400 Subject: [PATCH 051/140] IX: update required site id field to be more flexible (#1934) Co-authored-by: Joshua Gross --- adapters/ix/params_test.go | 59 ++++++++++++++++++++++++++++++++++++ static/bidder-params/ix.json | 18 +++++++++-- 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 adapters/ix/params_test.go 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/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"]} + ] } From be6c7497cea18ce64557b919c1ed830ecd8802bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Fern=C3=A1ndez?= Date: Fri, 30 Jul 2021 16:17:24 +0200 Subject: [PATCH 052/140] Axonix: enabled by default (#1936) --- adapters/axonix/axonix.go | 27 ++++++++++++++----- adapters/axonix/axonix_test.go | 16 +++++------ .../exemplary/banner-and-video.json | 10 +++---- .../exemplary/banner-video-native.json | 14 +++++----- .../axonixtest/exemplary/simple-banner.json | 6 ++--- .../axonixtest/exemplary/simple-video.json | 6 ++--- .../axonix/axonixtest/params/race/banner.json | 2 +- .../axonix/axonixtest/params/race/video.json | 2 +- .../supplemental/bad-response-no-body.json | 6 ++--- .../supplemental/status-bad-request.json | 6 ++--- .../supplemental/status-no-content.json | 6 ++--- .../supplemental/unexpected-status-code.json | 6 ++--- .../supplemental/valid-extension.json | 6 ++--- .../supplemental/valid-with-device.json | 6 ++--- adapters/axonix/params_test.go | 2 +- config/config.go | 2 +- 16 files changed, 68 insertions(+), 55 deletions(-) diff --git a/adapters/axonix/axonix.go b/adapters/axonix/axonix.go index d3016235319..268b0e3ee2f 100644 --- a/adapters/axonix/axonix.go +++ b/adapters/axonix/axonix.go @@ -4,25 +4,39 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "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 { - URI string + EndpointTemplate template.Template } func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + endpoint, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil { + return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) + } bidder := &adapter{ - URI: config.Endpoint, + EndpointTemplate: *endpoint, } return bidder, nil } +func (a *adapter) getEndpoint(ext *openrtb_ext.ExtImpAxonix) (string, error) { + endpointParams := macros.EndpointTemplateParams{ + AccountID: url.PathEscape(ext.SupplyId), + } + return macros.ResolveMacros(a.EndpointTemplate, endpointParams) +} + func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors []error @@ -44,9 +58,10 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte return nil, errors } - thisURI := a.URI - if len(thisURI) == 0 { - thisURI = "https://openrtb-us-east-1.axonix.com/supply/prebid-server/" + axonixExt.SupplyId + endpoint, err := a.getEndpoint(&axonixExt) + if err != nil { + errors = append(errors, err) + return nil, errors } requestJSON, err := json.Marshal(request) @@ -60,7 +75,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte requestData := &adapters.RequestData{ Method: "POST", - Uri: thisURI, + Uri: endpoint, Body: requestJSON, Headers: headers, } diff --git a/adapters/axonix/axonix_test.go b/adapters/axonix/axonix_test.go index 6c4a3eb34d6..fcac320075f 100644 --- a/adapters/axonix/axonix_test.go +++ b/adapters/axonix/axonix_test.go @@ -1,6 +1,7 @@ package axonix import ( + "github.com/stretchr/testify/assert" "testing" "github.com/prebid/prebid-server/adapters/adapterstest" @@ -8,9 +9,10 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -func TestJsonSamplesWithConfiguredURI(t *testing.T) { +func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{ - Endpoint: "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8"}) + Endpoint: "https://axonix.com/supply/prebid-server/{{.AccountID}}", + }) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -19,12 +21,8 @@ func TestJsonSamplesWithConfiguredURI(t *testing.T) { adapterstest.RunJSONBidderTest(t, "axonixtest", bidder) } -func TestJsonSamplesWithHardcodedURI(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{}) +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAxonix, config.Adapter{Endpoint: "{{Malformed}}"}) - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "axonixtest", bidder) + assert.Error(t, buildErr) } diff --git a/adapters/axonix/axonixtest/exemplary/banner-and-video.json b/adapters/axonix/axonixtest/exemplary/banner-and-video.json index 1755cd0ef22..04b640826dc 100644 --- a/adapters/axonix/axonixtest/exemplary/banner-and-video.json +++ b/adapters/axonix/axonixtest/exemplary/banner-and-video.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } }, @@ -32,7 +32,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -41,7 +41,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "imp": [ @@ -61,7 +61,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } }, @@ -75,7 +75,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/axonixtest/exemplary/banner-video-native.json b/adapters/axonix/axonixtest/exemplary/banner-video-native.json index 3944eb358b9..5dd5775004a 100644 --- a/adapters/axonix/axonixtest/exemplary/banner-video-native.json +++ b/adapters/axonix/axonixtest/exemplary/banner-video-native.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } }, @@ -30,7 +30,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } }, @@ -44,7 +44,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -53,7 +53,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "imp": [ @@ -73,7 +73,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } }, @@ -85,7 +85,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } }, @@ -99,7 +99,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/axonixtest/exemplary/simple-banner.json b/adapters/axonix/axonixtest/exemplary/simple-banner.json index 581e59b9b9e..40441a46425 100644 --- a/adapters/axonix/axonixtest/exemplary/simple-banner.json +++ b/adapters/axonix/axonixtest/exemplary/simple-banner.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -28,7 +28,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "imp": [ @@ -48,7 +48,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/axonixtest/exemplary/simple-video.json b/adapters/axonix/axonixtest/exemplary/simple-video.json index c15d7876470..56964b13565 100644 --- a/adapters/axonix/axonixtest/exemplary/simple-video.json +++ b/adapters/axonix/axonixtest/exemplary/simple-video.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "imp": [ @@ -36,7 +36,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/axonixtest/params/race/banner.json b/adapters/axonix/axonixtest/params/race/banner.json index 7217c9c394f..136be9b1517 100644 --- a/adapters/axonix/axonixtest/params/race/banner.json +++ b/adapters/axonix/axonixtest/params/race/banner.json @@ -1,3 +1,3 @@ { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } diff --git a/adapters/axonix/axonixtest/params/race/video.json b/adapters/axonix/axonixtest/params/race/video.json index 7217c9c394f..136be9b1517 100644 --- a/adapters/axonix/axonixtest/params/race/video.json +++ b/adapters/axonix/axonixtest/params/race/video.json @@ -1,3 +1,3 @@ { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } diff --git a/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json b/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json index f89f40f122d..a4704990ef6 100644 --- a/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json +++ b/adapters/axonix/axonixtest/supplemental/bad-response-no-body.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "imp": [ @@ -36,7 +36,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/axonixtest/supplemental/status-bad-request.json b/adapters/axonix/axonixtest/supplemental/status-bad-request.json index d64a855e348..7483da19834 100644 --- a/adapters/axonix/axonixtest/supplemental/status-bad-request.json +++ b/adapters/axonix/axonixtest/supplemental/status-bad-request.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "imp": [ @@ -36,7 +36,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/axonixtest/supplemental/status-no-content.json b/adapters/axonix/axonixtest/supplemental/status-no-content.json index 96dd899c1fb..7d4cd79589d 100644 --- a/adapters/axonix/axonixtest/supplemental/status-no-content.json +++ b/adapters/axonix/axonixtest/supplemental/status-no-content.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "imp": [ @@ -36,7 +36,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json b/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json index e7a7be7847e..fe0f7141bc8 100644 --- a/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json +++ b/adapters/axonix/axonixtest/supplemental/unexpected-status-code.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "imp": [ @@ -36,7 +36,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/axonixtest/supplemental/valid-extension.json b/adapters/axonix/axonixtest/supplemental/valid-extension.json index 372f24d4f76..c174d245bc8 100644 --- a/adapters/axonix/axonixtest/supplemental/valid-extension.json +++ b/adapters/axonix/axonixtest/supplemental/valid-extension.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -22,7 +22,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "imp": [ @@ -36,7 +36,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/axonixtest/supplemental/valid-with-device.json b/adapters/axonix/axonixtest/supplemental/valid-with-device.json index 62f4ea06b5a..50d48410e8a 100644 --- a/adapters/axonix/axonixtest/supplemental/valid-with-device.json +++ b/adapters/axonix/axonixtest/supplemental/valid-with-device.json @@ -16,7 +16,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } @@ -25,7 +25,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://openrtb-us-east-1.axonix.com/supply/prebid-server/24cc9034-f861-47b8-a6a8-b7e0968c00b8", + "uri": "https://axonix.com/supply/prebid-server/supply-test", "body": { "id": "test-request-id", "device": { @@ -43,7 +43,7 @@ }, "ext": { "bidder": { - "supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8" + "supplyId": "supply-test" } } } diff --git a/adapters/axonix/params_test.go b/adapters/axonix/params_test.go index e9c0cc5b83e..22b17a862f0 100644 --- a/adapters/axonix/params_test.go +++ b/adapters/axonix/params_test.go @@ -40,7 +40,7 @@ func TestInvalidParams(t *testing.T) { } var validParams = []string{ - `{"supplyId": "24cc9034-f861-47b8-a6a8-b7e0968c00b8"}`, + `{"supplyId": "test-supply"}`, `{"supplyId": "test"}`, } diff --git a/config/config.go b/config/config.go index 6e8b661a13b..b1d0553f38c 100644 --- a/config/config.go +++ b/config/config.go @@ -878,7 +878,7 @@ 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.axonix.endpoint", "https://openrtb-us-east-1.axonix.com/supply/prebid-server/{{.AccountID}}") 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") From 639a8bfc5de92efcb38918aa754152a0a56356eb Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Wed, 4 Aug 2021 06:49:19 +0300 Subject: [PATCH 053/140] Rubicon: buyer support (#1941) --- adapters/rubicon/rubicon.go | 76 +++++++++++-- adapters/rubicon/rubicon_test.go | 107 ++++++++++++++++++ .../rubicontest/exemplary/simple-video.json | 7 ++ 3 files changed, 180 insertions(+), 10 deletions(-) diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index e9627916cc6..1878c41feb0 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "net/url" + "strconv" "strings" "github.com/golang/glog" @@ -188,6 +189,21 @@ type rubiconUser struct { Language string `json:"language"` } +type rubiconBidResponse struct { + openrtb2.BidResponse + SeatBid []rubiconSeatBid `json:"seatbid,omitempty"` +} + +type rubiconSeatBid struct { + openrtb2.SeatBid + Buyer string `json:"buyer,omitempty"` +} + +type extPrebid struct { + Prebid *openrtb_ext.ExtBidPrebid `json:"prebid,omitempty"` + Bidder json.RawMessage `json:"bidder,omitempty"` +} + type rubiSize struct { w uint16 h uint16 @@ -1094,7 +1110,7 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external }} } - var bidResp openrtb2.BidResponse + var bidResp rubiconBidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: err.Error(), @@ -1119,9 +1135,17 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external cmpOverride := cmpOverrideFromBidRequest(internalRequest) for _, sb := range bidResp.SeatBid { + buyer, err := strconv.Atoi(sb.Buyer) + if err != nil { + buyer = 0 + } for i := 0; i < len(sb.Bid); i++ { bid := sb.Bid[i] + updatedBidExt := updateBidExtWithMetaNetworkId(bid, buyer) + if updatedBidExt != nil { + bid.Ext = updatedBidExt + } bidCmpOverride, ok := impToCpmOverride[bid.ImpID] if !ok || bidCmpOverride == 0 { bidCmpOverride = cmpOverride @@ -1148,15 +1172,6 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external return bidResponse, nil } -func cmpOverrideFromBidRequest(bidRequest *openrtb2.BidRequest) float64 { - var bidRequestExt bidRequestExt - if err := json.Unmarshal(bidRequest.Ext, &bidRequestExt); err != nil { - return 0 - } - - return bidRequestExt.Prebid.Bidders.Rubicon.Debug.CpmOverride -} - func mapImpIdToCpmOverride(imps []openrtb2.Imp) map[string]float64 { impIdToCmpOverride := make(map[string]float64) for _, imp := range imps { @@ -1174,3 +1189,44 @@ func mapImpIdToCpmOverride(imps []openrtb2.Imp) map[string]float64 { } return impIdToCmpOverride } + +func cmpOverrideFromBidRequest(bidRequest *openrtb2.BidRequest) float64 { + var bidRequestExt bidRequestExt + if err := json.Unmarshal(bidRequest.Ext, &bidRequestExt); err != nil { + return 0 + } + + return bidRequestExt.Prebid.Bidders.Rubicon.Debug.CpmOverride +} + +func updateBidExtWithMetaNetworkId(bid openrtb2.Bid, buyer int) json.RawMessage { + if buyer <= 0 { + return nil + } + var bidExt *extPrebid + if bid.Ext != nil { + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return nil + } + } + + if bidExt != nil { + if bidExt.Prebid != nil { + if bidExt.Prebid.Meta != nil { + bidExt.Prebid.Meta.NetworkID = buyer + } else { + bidExt.Prebid.Meta = &openrtb_ext.ExtBidPrebidMeta{NetworkID: buyer} + } + } else { + bidExt.Prebid = &openrtb_ext.ExtBidPrebid{Meta: &openrtb_ext.ExtBidPrebidMeta{NetworkID: buyer}} + } + } else { + bidExt = &extPrebid{Prebid: &openrtb_ext.ExtBidPrebid{Meta: &openrtb_ext.ExtBidPrebidMeta{NetworkID: buyer}}} + } + + marshalledExt, err := json.Marshal(&bidExt) + if err == nil { + return marshalledExt + } + return nil +} diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 28ddebbf5e3..a77592e1bb3 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "errors" + "github.com/buger/jsonparser" "github.com/stretchr/testify/mock" "io/ioutil" "net/http" @@ -38,6 +39,13 @@ type rubiAppendTrackerUrlTestScenario struct { expected string } +type rubiSetNetworkIdTestScenario struct { + bidExt *openrtb_ext.ExtBidPrebid + buyer string + expectedNetworkId int64 + isNetworkIdSet bool +} + type rubiTagInfo struct { code string zoneID int @@ -1674,6 +1682,105 @@ func TestOpenRTBResponseOverridePriceFromBidRequest(t *testing.T) { "Expected Price 10. Got: %s", bidResponse.Bids[0].Bid.Price) } +func TestOpenRTBResponseSettingOfNetworkId(t *testing.T) { + testScenarios := []rubiSetNetworkIdTestScenario{ + { + bidExt: nil, + buyer: "1", + expectedNetworkId: 1, + isNetworkIdSet: true, + }, + { + bidExt: nil, + buyer: "0", + expectedNetworkId: 0, + isNetworkIdSet: false, + }, + { + bidExt: nil, + buyer: "-1", + expectedNetworkId: 0, + isNetworkIdSet: false, + }, + { + bidExt: nil, + buyer: "1.1", + expectedNetworkId: 0, + isNetworkIdSet: false, + }, + { + bidExt: &openrtb_ext.ExtBidPrebid{}, + buyer: "2", + expectedNetworkId: 2, + isNetworkIdSet: true, + }, + { + bidExt: &openrtb_ext.ExtBidPrebid{Meta: &openrtb_ext.ExtBidPrebidMeta{}}, + buyer: "3", + expectedNetworkId: 3, + isNetworkIdSet: true, + }, + { + bidExt: &openrtb_ext.ExtBidPrebid{Meta: &openrtb_ext.ExtBidPrebidMeta{NetworkID: 5}}, + buyer: "4", + expectedNetworkId: 4, + isNetworkIdSet: true, + }, + { + bidExt: &openrtb_ext.ExtBidPrebid{Meta: &openrtb_ext.ExtBidPrebidMeta{NetworkID: 5}}, + buyer: "-1", + expectedNetworkId: 5, + isNetworkIdSet: false, + }, + } + + for _, scenario := range testScenarios { + request := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ + ID: "test-imp-id", + Banner: &openrtb2.Banner{}, + }}, + } + + requestJson, _ := json.Marshal(request) + reqData := &adapters.RequestData{ + Method: "POST", + Uri: "test-uri", + Body: requestJson, + Headers: nil, + } + + var givenBidExt json.RawMessage + if scenario.bidExt != nil { + marshalledExt, _ := json.Marshal(scenario.bidExt) + givenBidExt = marshalledExt + } else { + givenBidExt = nil + } + givenBidResponse := rubiconBidResponse{ + SeatBid: []rubiconSeatBid{{Buyer: scenario.buyer, + SeatBid: openrtb2.SeatBid{ + Bid: []openrtb2.Bid{{Price: 123.2, ImpID: "test-imp-id", Ext: givenBidExt}}}}}, + } + body, _ := json.Marshal(&givenBidResponse) + httpResp := &adapters.ResponseData{ + StatusCode: http.StatusOK, + Body: body, + } + + bidder := new(RubiconAdapter) + bidResponse, errs := bidder.MakeBids(request, reqData, httpResp) + assert.Empty(t, errs) + if scenario.isNetworkIdSet { + networkdId, err := jsonparser.GetInt(bidResponse.Bids[0].Bid.Ext, "prebid", "meta", "networkId") + assert.NoError(t, err) + assert.Equal(t, scenario.expectedNetworkId, networkdId) + } else { + assert.Equal(t, bidResponse.Bids[0].Bid.Ext, givenBidExt) + } + } +} + func TestOpenRTBResponseOverridePriceFromCorrespondingImp(t *testing.T) { request := &openrtb2.BidRequest{ ID: "test-request-id", diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 1daffe9b386..7e5274f2c9b 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -315,6 +315,7 @@ "id": "test-request-id", "seatbid": [ { + "buyer": "123", "bid": [ { "id": "test_bid_id", @@ -326,6 +327,9 @@ "dealid": "test_dealid", "ext": { "prebid": { + "meta": { + "networkId": 123 + }, "type": "video" } } @@ -354,6 +358,9 @@ "dealid": "test_dealid", "ext": { "prebid": { + "meta": { + "networkId": 123 + }, "type": "video" } } From bca2fb1c5e4f30fafe87420193cac439a1aa90c8 Mon Sep 17 00:00:00 2001 From: Michael Burns Date: Wed, 4 Aug 2021 10:27:27 -0400 Subject: [PATCH 054/140] avoid htmlescaping in json encoding for native (#1944) --- adapters/ix/ix.go | 22 +++++++++++++++++-- .../native-eventtrackers-compat-12.json | 4 ++-- .../native-eventtrackers-empty.json | 4 ++-- .../native-eventtrackers-missing.json | 4 ++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index b251ec0f736..c79eda31040 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "sort" + "strings" "github.com/mxmCherry/openrtb/v15/native1" native1response "github.com/mxmCherry/openrtb/v15/native1/response" @@ -425,7 +426,7 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque 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 { + if json, err := marshalJsonWithoutUnicode(bidNative1v1); err == nil { bid.AdM = string(json) } } @@ -436,7 +437,7 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque err := json.Unmarshal([]byte(bid.AdM), &bidNative1v2) if err == nil && len(bidNative1v2.EventTrackers) > 0 { mergeNativeImpTrackers(bidNative1v2) - if json, err := json.Marshal(bidNative1v2); err == nil { + if json, err := marshalJsonWithoutUnicode(bidNative1v2); err == nil { bid.AdM = string(json) } } @@ -499,3 +500,20 @@ func mergeNativeImpTrackers(bidNative *native1response.Response) { // sort so tests pass correctly sort.Strings(bidNative.ImpTrackers) } + +func marshalJsonWithoutUnicode(v interface{}) (string, error) { + // json.Marshal uses HTMLEscape for strings inside JSON which affects URLs + // this is a problem with Native responses that embed JSON within JSON + // a custom encoder can be used to disable this encoding. + // https://pkg.go.dev/encoding/json#Marshal + // https://pkg.go.dev/encoding/json#Encoder.SetEscapeHTML + sb := &strings.Builder{} + encoder := json.NewEncoder(sb) + encoder.SetEscapeHTML(false) + if err := encoder.Encode(v); err != nil { + return "", err + } + // json.Encode also writes a newline, need to remove + // https://pkg.go.dev/encoding/json#Encoder.Encode + return strings.TrimSuffix(sb.String(), "\n"), nil +} diff --git a/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json index 36a239987a6..38fb73c4a2b 100644 --- a/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json +++ b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json @@ -51,7 +51,7 @@ "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\"]}", + "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://x123.casalemedia.com/ifnotify?a=123&b=&c=741efbc7-f28b-5301-a18d-372a4c4226c3&d=0.123\",\"event\":1,\"method\":1}],\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}", "adomain": [ "https://advertiser.example.com" ], @@ -83,7 +83,7 @@ "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\"}]}", + "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://x123.casalemedia.com/ifnotify?a=123&b=&c=741efbc7-f28b-5301-a18d-372a4c4226c3&d=0.123\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"https://example.com/imp-2.gif\"},{\"event\":1,\"method\":1,\"url\":\"https://x123.casalemedia.com/ifnotify?a=123&b=&c=741efbc7-f28b-5301-a18d-372a4c4226c3&d=0.123\"}]}", "adomain": [ "https://advertiser.example.com" ], diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json index 4cf314e742f..37f77ff49bd 100644 --- a/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json +++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json @@ -51,7 +51,7 @@ "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\":[]}}", + "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" ], @@ -83,7 +83,7 @@ "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\":[]}}", + "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" ], diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json index d8c78a5cbca..d87332665fa 100644 --- a/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json +++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json @@ -51,7 +51,7 @@ "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\"]}}", + "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" ], @@ -83,7 +83,7 @@ "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\"]}}", + "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" ], From cb1193a965c722a885f74b43c4583523e87a46b0 Mon Sep 17 00:00:00 2001 From: ym-winston <46379634+ym-winston@users.noreply.github.com> Date: Thu, 5 Aug 2021 13:14:20 -0400 Subject: [PATCH 055/140] add support for reading pbadslot in imp[].ext.data and outputting it as imp.ext.gpid in yieldmo request (#1935) --- adapters/yieldmo/yieldmo.go | 18 ++- .../yieldmotest/exemplary/with_gpid.json | 105 ++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 adapters/yieldmo/yieldmotest/exemplary/with_gpid.json diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index 7d7a8f22b01..eaaa489d722 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -16,8 +16,18 @@ type YieldmoAdapter struct { endpoint string } +type ExtImpBidderYieldmo struct { + adapters.ExtImpBidder + Data *ExtData `json:"data,omitempty"` +} + +type ExtData struct { + PbAdslot string `json:"pbadslot"` +} + type Ext struct { PlacementId string `json:"placement_id"` + Gpid string `json:"gpid,omitempty"` } func (a *YieldmoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -62,7 +72,7 @@ func (a *YieldmoAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.Re func preprocess(request *openrtb2.BidRequest) error { for i := 0; i < len(request.Imp); i++ { var imp = request.Imp[i] - var bidderExt adapters.ExtImpBidder + var bidderExt ExtImpBidderYieldmo if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -81,6 +91,12 @@ func preprocess(request *openrtb2.BidRequest) error { var impExt Ext impExt.PlacementId = yieldmoExt.PlacementId + if bidderExt.Data != nil { + if bidderExt.Data.PbAdslot != "" { + impExt.Gpid = bidderExt.Data.PbAdslot + } + } + impExtJSON, err := json.Marshal(impExt) if err != nil { return &errortypes.BadInput{ diff --git a/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json b/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json new file mode 100644 index 00000000000..bd9e911058a --- /dev/null +++ b/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": "123" + }, + "data": { + "adserver": { + "name": "adserver_name", + "adslot": "adserver_adslot" + }, + "pbadslot": "pbadslot_1" + } + } + } + ], + "site": { + "id": "fake-site-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ads.yieldmo.com/openrtb2", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "placement_id": "123", + "gpid": "pbadslot_1" + } + } + ], + "site": { + "id": "fake-site-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "yieldmo", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} From 6c3e7f0780cb09505d9f46396c521bf6451c8bd1 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 5 Aug 2021 11:18:47 -0700 Subject: [PATCH 056/140] Mirror stored imp data to bid response (#1889) --- endpoints/openrtb2/auction.go | 45 +++- endpoints/openrtb2/auction_test.go | 369 +++++++++++++++++++++++------ exchange/exchange.go | 62 +++-- exchange/exchange_test.go | 167 ++++++++++++- openrtb_ext/bid.go | 4 + util/jsonutil/jsonutil.go | 47 +++- util/jsonutil/jsonutil_test.go | 208 +++++++++++++++- 7 files changed, 775 insertions(+), 127 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index a6b3479a36c..bcdf133cd2a 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -135,7 +135,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http deps.analytics.LogAuctionObject(&ao) }() - req, errL := deps.parseRequest(r) + req, impExtInfoMap, errL := deps.parseRequest(r) if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { return @@ -192,6 +192,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http LegacyLabels: labels, Warnings: warnings, GlobalPrivacyControlHeader: secGPC, + ImpExtInfoMap: impExtInfoMap, } response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) @@ -234,7 +235,7 @@ 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 *openrtb_ext.RequestWrapper, errs []error) { +func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, errs []error) { req = &openrtb_ext.RequestWrapper{} req.BidRequest = &openrtb2.BidRequest{} errs = nil @@ -262,7 +263,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ defer cancel() // Fetch the Stored Request data and merge it into the HTTP request. - if requestJson, errs = deps.processStoredRequests(ctx, requestJson); len(errs) > 0 { + if requestJson, impExtInfoMap, errs = deps.processStoredRequests(ctx, requestJson); len(errs) > 0 { return } @@ -1316,15 +1317,15 @@ func getJsonSyntaxError(testJSON []byte) (bool, string) { return false, "" } -func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson []byte) ([]byte, []error) { +func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson []byte) ([]byte, map[string]exchange.ImpExtInfo, []error) { // Parse the Stored Request IDs from the BidRequest and Imps. storedBidRequestId, hasStoredBidRequest, err := getStoredRequestId(requestJson) if err != nil { - return nil, []error{err} + return nil, nil, []error{err} } imps, impIds, idIndices, errs := parseImpInfo(requestJson) if len(errs) > 0 { - return nil, errs + return nil, nil, errs } // Fetch the Stored Request data @@ -1334,7 +1335,7 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson } storedRequests, storedImps, errs := deps.storedReqFetcher.FetchRequests(ctx, storedReqIds, impIds) if len(errs) != 0 { - return nil, errs + return nil, nil, errs } // Apply the Stored BidRequest, if it exists @@ -1352,7 +1353,7 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson err = fmt.Errorf("ext.prebid.storedrequest.id refers to Stored Request %s which contains Invalid JSON: %s", storedBidRequestId, Err) } } - return nil, []error{err} + return nil, nil, []error{err} } } @@ -1369,7 +1370,7 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson err = fmt.Errorf("Invalid JSON in Default Request Settings: %s", Err) } } - return nil, []error{err} + return nil, nil, []error{err} } resolvedRequest = aliasedRequest } @@ -1377,8 +1378,10 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson // Apply any Stored Imps, if they exist. Since the JSON Merge Patch overrides arrays, // and Prebid Server defers to the HTTP Request to resolve conflicts, it's safe to // assume that the request.imp data did not change when applying the Stored BidRequest. + impExtInfoMap := make(map[string]exchange.ImpExtInfo, len(impIds)) for i := 0; i < len(impIds); i++ { resolvedImp, err := jsonpatch.MergePatch(storedImps[impIds[i]], imps[idIndices[i]]) + if err != nil { hasErr, Err := getJsonSyntaxError(imps[idIndices[i]]) if hasErr { @@ -1389,22 +1392,38 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson err = fmt.Errorf("imp.ext.prebid.storedrequest.id %s: Stored Imp has Invalid JSON: %s", impIds[i], Err) } } - return nil, []error{err} + return nil, nil, []error{err} } imps[idIndices[i]] = resolvedImp + + impId, err := jsonparser.GetString(resolvedImp, "id") + if err != nil { + return nil, nil, []error{err} + } + // This is substantially faster for reading values from a json blob than the Go json package, + // but keep in mind that each jsonparser.GetXXX call re-parses the entire json. + // Based on performance measurements, the tipping point of efficiency is around 4 calls. + // At that point, please consider switching to EachKey to use a single pass. + includeVideoAttributes, err := jsonparser.GetBoolean(resolvedImp, "ext", "prebid", "options", "echovideoattrs") + if err != nil && err != jsonparser.KeyPathNotFoundError { + return nil, nil, []error{err} + } + if storedImps[impIds[i]] != nil { + impExtInfoMap[impId] = exchange.ImpExtInfo{EchoVideoAttrs: includeVideoAttributes, StoredImp: storedImps[impIds[i]]} + } } if len(impIds) > 0 { newImpJson, err := json.Marshal(imps) if err != nil { - return nil, []error{err} + return nil, nil, []error{err} } resolvedRequest, err = jsonparser.Set(resolvedRequest, newImpJson, "imp") if err != nil { - return nil, []error{err} + return nil, nil, []error{err} } } - return resolvedRequest, nil + return resolvedRequest, impExtInfoMap, nil } // parseImpInfo parses the request JSON and returns several things about the Imps diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 4835dd92943..ec373094864 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -18,7 +18,6 @@ import ( "time" "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" @@ -1020,8 +1019,10 @@ func TestStoredRequests(t *testing.T) { hardcodedResponseIPValidator{response: true}, } + testStoreVideoAttr := []bool{true, true, false, false} + for i, requestData := range testStoredRequests { - newRequest, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData)) + newRequest, impExtInfoMap, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData)) if len(errList) != 0 { for _, err := range errList { if err != nil { @@ -1032,9 +1033,50 @@ func TestStoredRequests(t *testing.T) { } } expectJson := json.RawMessage(testFinalRequests[i]) - if !jsonpatch.Equal(newRequest, expectJson) { - t.Errorf("Error in processStoredRequests, test %d failed on compare\nFound:\n%s\nExpected:\n%s", i, string(newRequest), string(expectJson)) + + assert.JSONEq(t, string(expectJson), string(newRequest), "Incorrect result request %d", i) + + expectedImp := testStoredImpIds[i] + expectedStoredImp := json.RawMessage(testStoredImps[i]) + if len(impExtInfoMap[expectedImp].StoredImp) > 0 { + assert.JSONEq(t, string(expectedStoredImp), string(impExtInfoMap[expectedImp].StoredImp), "Incorrect expected stored imp %d", i) + } + assert.Equalf(t, testStoreVideoAttr[i], impExtInfoMap[expectedImp].EchoVideoAttrs, "EchoVideoAttrs value is incorrect") + } +} + +func TestStoredRequestsVideoErrors(t *testing.T) { + deps := &endpointDeps{ + &nobidExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + newTestMetrics(), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + // tests processStoredRequests function behavior in parsing incorrect input related to echovideoattrs feature + // this test now has 2 scenarios: + // 1) expected value we get using jsonparser.GetBoolean is integer + // 2) imp id is not found using jsonparser.GetString + // this loop iterates over testStoredRequestsErrors where input json has incorrect set up + // testStoredRequestsErrorsResults variable contains error message for every iteration + + for i, requestData := range testStoredRequestsErrors { + _, _, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData)) + + assert.NotEmpty(t, errList, "processStoredRequests should return error") + assert.Contains(t, errList[0].Error(), testStoredRequestsErrorsResults[i], "Incorrect error") } } @@ -2902,7 +2944,24 @@ var testStoredRequestData = map[string]json.RawMessage{ // third below has valid JSON and matches schema var testStoredImpData = map[string]json.RawMessage{ "1": json.RawMessage(`{ -"id": "adUnit1", + "id": "adUnit1", + "ext": { + "appnexus": { + "placementId": "abc", + "position": "above", + "reserve": 0.35 + }, + "rubicon": { + "accountId": "abc" + } + }, + "video":{ + "w":200, + "h":300 + } + }`), + "2": json.RawMessage(`{ + "id": "adUnit1", "ext": { "appnexus": { "placementId": "abc", @@ -2915,7 +2974,7 @@ var testStoredImpData = map[string]json.RawMessage{ } }`), "7": json.RawMessage(`{ -"id": "adUnit1", + "id": "adUnit1", "ext": { "appnexus": { "placementId": 12345678, @@ -2944,6 +3003,15 @@ var testStoredImpData = map[string]json.RawMessage{ } } }`), + "10": json.RawMessage(`{ + "ext": { + "appnexus": { + "placementId": 12345678, + "position": "above", + "reserve": 0.35 + } + } + }`), } // Incoming requests with stored request IDs @@ -2952,10 +3020,17 @@ var testStoredRequests = []string{ "id": "ThisID", "imp": [ { + "video":{ + "h":300, + "w":200 + }, "ext": { "prebid": { "storedrequest": { "id": "1" + }, + "options": { + "echovideoattrs": true } } } @@ -2980,6 +3055,9 @@ var testStoredRequests = []string{ "prebid": { "storedrequest": { "id": "1" + }, + "options": { + "echovideoattrs": true } }, "appnexus": { @@ -3008,7 +3086,10 @@ var testStoredRequests = []string{ "ext": { "prebid": { "storedrequest": { - "id": "1" + "id": "2" + }, + "options": { + "echovideoattrs": false } } } @@ -3059,25 +3140,22 @@ var testStoredRequests = []string{ }`, } -// The expected requests after stored request processing -var testFinalRequests = []string{ +var testStoredRequestsErrors = []string{ `{ "id": "ThisID", "imp": [ { - "id": "adUnit1", + "video":{ + "h":300, + "w":200 + }, "ext": { - "appnexus": { - "placementId": "abc", - "position": "above", - "reserve": 0.35 - }, - "rubicon": { - "accountId": "abc" - }, "prebid": { "storedrequest": { "id": "1" + }, + "options": { + "echovideoattrs": 1 } } } @@ -3092,22 +3170,22 @@ var testFinalRequests = []string{ } } } - }`, - `{ + }`, `{ "id": "ThisID", "imp": [ { - "id": "adUnit2", + "video":{ + "h":300, + "w":200 + }, "ext": { "prebid": { "storedrequest": { - "id": "1" + "id": "10" + }, + "options": { + "echovideoattrs": true } - }, - "appnexus": { - "placementId": "def", - "position": "above", - "trafficSourceCode": "mysite.com" } } } @@ -3122,56 +3200,220 @@ var testFinalRequests = []string{ } } }`, +} + +var testStoredRequestsErrorsResults = []string{ + "Value is not a boolean: 1", + "Key path not found", +} + +// The expected requests after stored request processing +var testFinalRequests = []string{ `{ "id": "ThisID", "imp": [ { - "id": "adUnit1", - "ext": { - "appnexus": { - "placementId": "abc", - "position": "above", - "reserve": 0.35 - }, - "rubicon": { - "accountId": "abc" + "video":{ + "h":300, + "w":200 + }, + "ext":{ + "appnexus":{ + "placementId":"abc", + "position":"above", + "reserve":0.35 }, - "prebid": { - "storedrequest": { - "id": "1" - } + "prebid":{ + "storedrequest":{ + "id":"1" + }, + "options":{ + "echovideoattrs":true } + }, + "rubicon":{ + "accountId":"abc" } + }, + "id":"adUnit1" } ], - "tmax": 500, "ext": { "prebid": { + "cache": { + "markup": 1 + }, "targeting": { - "pricegranularity": "low" + } + } +} + }`, + `{ + "id": "ThisID", + "imp": [ + { + "video":{ + "w":200, + "h":300 + }, + "ext":{ + "appnexus":{ + "placementId":"def", + "position":"above", + "trafficSourceCode":"mysite.com" + }, + "prebid":{ + "storedrequest":{ + "id":"1" + }, + "options":{ + "echovideoattrs":true + } + } }, - "storedrequest": { - "id": "2" + "id":"adUnit2" + } + ], + "ext": { + "prebid": { + "cache": { + "markup": 1 + }, + "targeting": { } } } }`, + `{ + "ext": { + "prebid": { + "storedrequest": { + "id": "2" + }, + "targeting": { + "pricegranularity": "low" + } + } + }, + "id": "ThisID", + "imp": [ + { + "ext": { + "appnexus": { + "placementId": "abc", + "position": "above", + "reserve": 0.35 + }, + "prebid": { + "storedrequest": { + "id": "2" + }, + "options":{ + "echovideoattrs":false + } + }, + "rubicon": { + "accountId": "abc" + } + }, + "id": "adUnit1" + } + ], + "tmax": 500 + } +`, `{ "id": "ThisID", "imp": [ { - "id": "some-static-imp", - "video":{ - "mimes":["video/mp4"] + "id": "some-static-imp", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": "abc", + "position": "below" + } + } + }, + { + "ext": { + "appnexus": { + "placementId": "abc", + "position": "above", + "reserve": 0.35 + }, + "prebid": { + "storedrequest": { + "id": "1" + } + }, + "rubicon": { + "accountId": "abc" + } + }, + "id": "adUnit1", + "video":{ + "w":200, + "h":300 + } + } + ], + "ext": { + "prebid": { + "cache": { + "markup": 1 }, - "ext": { - "appnexus": { - "placementId": "abc", - "position": "below" - } + "targeting": { } - }, - { + } + } +}`, +} + +var testStoredImpIds = []string{ + "adUnit1", "adUnit2", "adUnit1", "some-static-imp", +} + +var testStoredImps = []string{ + `{ + "id": "adUnit1", + "ext": { + "appnexus": { + "placementId": "abc", + "position": "above", + "reserve": 0.35 + }, + "rubicon": { + "accountId": "abc" + } + }, + "video":{ + "w":200, + "h":300 + } + }`, + `{ + "id": "adUnit1", + "ext": { + "appnexus": { + "placementId": "abc", + "position": "above", + "reserve": 0.35 + }, + "rubicon": { + "accountId": "abc" + } + }, + "video":{ + "w":200, + "h":300 + } + }`, + `{ "id": "adUnit1", "ext": { "appnexus": { @@ -3181,25 +3423,10 @@ var testFinalRequests = []string{ }, "rubicon": { "accountId": "abc" - }, - "prebid": { - "storedrequest": { - "id": "1" - } } } - } - ], - "ext": { - "prebid": { - "cache": { - "markup": 1 - }, - "targeting": { - } - } - } -}`, + }`, + ``, } type mockStoredReqFetcher struct { diff --git a/exchange/exchange.go b/exchange/exchange.go index 6aaafcd1ad6..b95641c82b5 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,11 +14,10 @@ import ( "strings" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/stored_requests" - + "github.com/buger/jsonparser" "github.com/gofrs/uuid" "github.com/golang/glog" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -27,6 +26,7 @@ import ( "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/stored_requests" ) type ContextKey string @@ -135,6 +135,11 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid } } +type ImpExtInfo struct { + EchoVideoAttrs bool + StoredImp []byte +} + // AuctionRequest holds the bid request for the auction // and all other information needed to process that request type AuctionRequest struct { @@ -145,6 +150,7 @@ type AuctionRequest struct { StartTime time.Time Warnings []error GlobalPrivacyControlHeader string + ImpExtInfoMap map[string]ImpExtInfo // LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. @@ -303,7 +309,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } // Build the response - return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs) + return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, errs) } func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) gdpr.Signal { @@ -452,6 +458,7 @@ func (e *exchange) getAllBids( reqInfo := adapters.NewExtraRequestInfo(conversions) reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader + bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) // Add in time reporting @@ -589,7 +596,7 @@ func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage { } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester -func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, errList []error) (*openrtb2.BidResponse, error) { +func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, errList []error) (*openrtb2.BidResponse, error) { bidResponse := new(openrtb2.BidResponse) var err error @@ -605,7 +612,7 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ for _, a := range liveAdapters { //while processing every single bib, do we need to handle categories here? if adapterBids[a] != nil && len(adapterBids[a].bids) > 0 { - sb := e.makeSeatBid(adapterBids[a], a, adapterExtra, auc, returnCreative) + sb := e.makeSeatBid(adapterBids[a], a, adapterExtra, auc, returnCreative, impExtInfoMap) seatBids = append(seatBids, *sb) bidResponse.Cur = adapterBids[a].currency } @@ -899,14 +906,14 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb // Return an openrtb seatBid for a bidder // BuildBidResponse is responsible for ensuring nil bid seatbids are not included -func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool) *openrtb2.SeatBid { +func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo) *openrtb2.SeatBid { seatBid := &openrtb2.SeatBid{ Seat: adapter.String(), Group: 0, // Prebid cannot support roadblocking } var errList []error - seatBid.Bid, errList = e.makeBid(adapterBid.bids, auc, returnCreative) + seatBid.Bid, errList = e.makeBid(adapterBid.bids, auc, returnCreative, impExtInfoMap) if len(errList) > 0 { adapterExtra[adapter].Errors = append(adapterExtra[adapter].Errors, errsToBidderErrors(errList)...) } @@ -914,7 +921,7 @@ func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.B return seatBid } -func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool) ([]openrtb2.Bid, []error) { +func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo) ([]openrtb2.Bid, []error) { result := make([]openrtb2.Bid, 0, len(bids)) errs := make([]error, 0, 1) @@ -935,7 +942,7 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool } } - if bidExtJSON, err := makeBidExtJSON(bid.bid.Ext, bidExtPrebid); err != nil { + if bidExtJSON, err := makeBidExtJSON(bid.bid.Ext, bidExtPrebid, impExtInfoMap, bid.bid.ImpID); err != nil { errs = append(errs, err) } else { result = append(result, *bid.bid) @@ -949,19 +956,34 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool return result, errs } -func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid) (json.RawMessage, error) { - // no existing bid.ext. generate a bid.ext with just our prebid section populated. - if len(ext) == 0 { - bidExt := &openrtb_ext.ExtBid{Prebid: prebid} - return json.Marshal(bidExt) - } - - // update existing bid.ext with our prebid section. if bid.ext.prebid already exists, it will be overwritten. +func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string) (json.RawMessage, error) { + // update existing bid.ext with prebid section + // if bid.ext.prebid already exists, it will be overwritten. + // if echoVideoAttrs set to true stored video attributes will be added to bid.ext.storedrequestattributes var extMap map[string]interface{} - if err := json.Unmarshal(ext, &extMap); err != nil { - return nil, err + + if len(ext) != 0 { + if err := json.Unmarshal(ext, &extMap); err != nil { + return nil, err + } + } else { + extMap = make(map[string]interface{}) } + extMap[openrtb_ext.PrebidExtKey] = prebid + + if impExtInfo, ok := impExtInfoMap[impId]; ok && impExtInfo.EchoVideoAttrs { + + videoData, _, _, err := jsonparser.Get(impExtInfo.StoredImp, "video") + if err != nil && err != jsonparser.KeyPathNotFoundError { + return nil, err + } + //handler for case where EchoVideoAttrs is true, but video data is not found + if len(videoData) > 0 { + extMap[openrtb_ext.StoredRequestAttributes] = json.RawMessage(videoData) + } + } + return json.Marshal(extMap) } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 989e67078eb..ba2d73b4824 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -149,7 +149,7 @@ func TestCharacterEscape(t *testing.T) { var errList []error // 4) Build bid response - bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, errList) + bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, errList) // 5) Assert we have no errors and one '&' character as we are supposed to if err != nil { @@ -1307,7 +1307,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { var errList []error // 4) Build bid response - bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, errList) + bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, errList) // 5) Assert we have no errors and the bid response we expected assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error") @@ -1400,7 +1400,7 @@ func TestBidReturnsCreative(t *testing.T) { //Run tests for _, test := range testCases { - resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative) + resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative, nil) assert.Equal(t, 0, len(resultingErrs), "%s. Test should not return errors \n", test.description) assert.Equal(t, test.expectedCreativeMarkup, resultingBids[0].AdM, "%s. Ad markup string doesn't match expected \n", test.description) @@ -1685,12 +1685,64 @@ func TestBidResponseCurrency(t *testing.T) { } // Run tests for i := range testCases { - actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, errList) + actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, errList) assert.NoError(t, err, fmt.Sprintf("[TEST_FAILED] e.buildBidResponse resturns error in test: %s Error message: %s \n", testCases[i].description, err)) assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext)) } } +func TestBidResponseImpExtInfo(t *testing.T) { + // Init objects + cfg := &config.Configuration{Adapters: make(map[string]config.Adapter, 1)} + cfg.Adapters["appnexus"] = config.Adapter{Endpoint: "http://ib.adnxs.com"} + + e := NewExchange(nil, nil, cfg, &metricsConf.DummyMetricsEngine{}, nil, gdpr.AlwaysAllow{}, nil, nilCategoryFetcher{}).(*exchange) + + liveAdapters := make([]openrtb_ext.BidderName, 1) + liveAdapters[0] = "appnexus" + + bidRequest := &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + Video: &openrtb2.Video{}, + Ext: json.RawMessage(`{"appnexus": {"placementId": 10433394}}`), + }}, + Ext: json.RawMessage(``), + } + + var errList []error + + sampleBid := &openrtb2.Bid{ + ID: "some-imp-id", + ImpID: "some-impression-id", + W: 300, + H: 250, + Ext: nil, + } + aPbsOrtbBidArr := []*pbsOrtbBid{{bid: sampleBid, bidType: openrtb_ext.BidTypeVideo}} + + adapterBids := map[openrtb_ext.BidderName]*pbsOrtbSeatBid{ + openrtb_ext.BidderName("appnexus"): { + bids: aPbsOrtbBidArr, + }, + } + + impExtInfo := make(map[string]ImpExtInfo, 1) + impExtInfo["some-impression-id"] = ImpExtInfo{ + true, + []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)} + + expectedBidResponseExt := `{"prebid":{"type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}` + + actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, errList) + assert.NoError(t, err, fmt.Sprintf("imp ext info was not passed through correctly: %s", err)) + + resBidExt := string(actualBidResp.SeatBid[0].Bid[0].Ext) + assert.Equalf(t, expectedBidResponseExt, resBidExt, "Expected bid response extension is incorrect") + +} + // TestRaceIntegration runs an integration test using all the sample params from // adapters/{bidder}/{bidder}test/params/race/*.json files. // @@ -3443,6 +3495,113 @@ func TestUpdateHbPbCatDur(t *testing.T) { } } +func TestMakeBidExtJSON(t *testing.T) { + + type aTest struct { + description string + ext json.RawMessage + extBidPrebid openrtb_ext.ExtBidPrebid + impExtInfo map[string]ImpExtInfo + expectedBidExt string + expectedErrMessage string + } + + testCases := []aTest{ + { + description: "Valid extension, non empty extBidPrebid and valid imp ext info", + ext: json.RawMessage(`{"video":{"h":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}}, + expectedBidExt: `{"prebid":{"type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}}`, + expectedErrMessage: "", + }, + { + description: "Empty extension, non empty extBidPrebid and valid imp ext info", + ext: nil, + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}}, + expectedBidExt: `{"prebid":{"type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, + expectedErrMessage: "", + }, + { + description: "Valid extension, non empty extBidPrebid and imp ext info not found", + ext: json.RawMessage(`{"video":{"h":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, + impExtInfo: map[string]ImpExtInfo{"another_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}}, + expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}}`, + expectedErrMessage: "", + }, + { + description: "Valid extension, empty extBidPrebid and valid imp ext info", + ext: json.RawMessage(`{"video":{"h":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}}, + expectedBidExt: `{"prebid":{"type":""},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}}`, + expectedErrMessage: "", + }, + { + description: "Valid extension, non empty extBidPrebid and empty imp ext info", + ext: json.RawMessage(`{"video":{"h":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, + impExtInfo: nil, + expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}}`, + expectedErrMessage: "", + }, + { + description: "Valid extension, non empty extBidPrebid and valid imp ext info without video attr", + ext: json.RawMessage(`{"video":{"h":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`)}}, + expectedBidExt: `{"prebid":{"type":"video"},"video":{"h":100}}`, + expectedErrMessage: "", + }, + { + description: "Valid extension with prebid, non empty extBidPrebid and valid imp ext info without video attr", + ext: json.RawMessage(`{"prebid":{"targeting":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`)}}, + expectedBidExt: `{"prebid":{"type":"video"}}`, + expectedErrMessage: "", + }, + { + description: "Valid extension with prebid, non empty extBidPrebid and valid imp ext info with video attr", + ext: json.RawMessage(`{"prebid":{"targeting":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}}, + expectedBidExt: `{"prebid":{"type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, + expectedErrMessage: "", + }, + //Error cases + { + description: "Invalid extension, valid extBidPrebid and valid imp ext info", + ext: json.RawMessage(`{invalid json}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}}, + expectedBidExt: ``, + expectedErrMessage: "invalid character", + }, + { + description: "Valid extension, empty extBidPrebid and invalid imp ext info", + ext: json.RawMessage(`{"video":{"h":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{!}}`)}}, + expectedBidExt: ``, + expectedErrMessage: "invalid character", + }, + } + + for _, test := range testCases { + result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id") + + if test.expectedErrMessage == "" { + assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result") + assert.NoError(t, err, "Error should not be returned") + } else { + assert.Contains(t, err.Error(), test.expectedErrMessage, "incorrect error message") + } + } +} + type exchangeSpec struct { GDPREnabled bool `json:"gdpr_enabled"` IncomingRequest exchangeRequest `json:"incomingRequest"` diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 535ac76be04..ce6e25fd0e7 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -157,3 +157,7 @@ func min(x, y int) int { } return y } + +const ( + StoredRequestAttributes = "storedrequestattributes" +) diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go index 963477de7aa..4fd56745301 100644 --- a/util/jsonutil/jsonutil.go +++ b/util/jsonutil/jsonutil.go @@ -7,8 +7,10 @@ import ( ) var comma = []byte(",")[0] +var colon = []byte(":")[0] -func DropElement(extension []byte, elementName string) ([]byte, error) { +func findElementIndexes(extension []byte, elementName string) (bool, int64, int64, error) { + found := false buf := bytes.NewBuffer(extension) dec := json.NewDecoder(buf) var startIndex int64 @@ -20,14 +22,15 @@ func DropElement(extension []byte, elementName string) ([]byte, error) { break } if err != nil { - return nil, err + return false, -1, -1, err } if token == elementName { err := dec.Decode(&i) if err != nil { - return nil, err + return false, -1, -1, err } + found = true endIndex := dec.InputOffset() if dec.More() { @@ -45,13 +48,43 @@ func DropElement(extension []byte, elementName string) ([]byte, error) { endIndex++ } } - - extension = append(extension[:startIndex], extension[endIndex:]...) - break + return found, startIndex, endIndex, nil } else { startIndex = dec.InputOffset() } } - return extension, nil + + return false, -1, -1, nil +} + +func DropElement(extension []byte, elementName string) ([]byte, error) { + found, startIndex, endIndex, err := findElementIndexes(extension, elementName) + if found { + extension = append(extension[:startIndex], extension[endIndex:]...) + } + return extension, err +} + +func FindElement(extension []byte, elementName string) (bool, []byte, error) { + + found, startIndex, endIndex, err := findElementIndexes(extension, elementName) + + if found && err == nil { + element := extension[startIndex:endIndex] + index := 0 + for { + if index < len(element) && element[index] != colon { + index++ + } else { + index++ + break + } + } + element = element[index:] + return found, element, err + } + + return found, nil, err + } diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go index 0b6ec34c4ed..d8738e171c7 100644 --- a/util/jsonutil/jsonutil_test.go +++ b/util/jsonutil/jsonutil_test.go @@ -7,7 +7,6 @@ import ( ) func TestDropElement(t *testing.T) { - tests := []struct { description string input []byte @@ -17,7 +16,7 @@ func TestDropElement(t *testing.T) { errorContains string }{ { - description: "Drop Single Element After Another Element", + 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}}`), @@ -25,7 +24,7 @@ func TestDropElement(t *testing.T) { errorContains: "", }, { - description: "Drop Single Element Before Another Element", + 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}}`), @@ -33,7 +32,7 @@ func TestDropElement(t *testing.T) { errorContains: "", }, { - description: "Drop Single Element", + 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": {}}`), @@ -41,7 +40,7 @@ func TestDropElement(t *testing.T) { errorContains: "", }, { - description: "Drop Single Element string", + 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": {}}`), @@ -49,7 +48,7 @@ func TestDropElement(t *testing.T) { errorContains: "", }, { - description: "Drop Parent Element Between Two Elements", + 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}`), @@ -57,7 +56,7 @@ func TestDropElement(t *testing.T) { errorContains: "", }, { - description: "Drop Parent Element Before Element", + 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}`), @@ -65,7 +64,7 @@ func TestDropElement(t *testing.T) { errorContains: "", }, { - description: "Drop Parent Element After Element", + 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"}`), @@ -73,7 +72,7 @@ func TestDropElement(t *testing.T) { errorContains: "", }, { - description: "Drop Parent Element Only", + description: "Drop parent element only", input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), elementToRemove: "consented_providers_settings", output: []byte(`{}`), @@ -81,7 +80,7 @@ func TestDropElement(t *testing.T) { errorContains: "", }, { - description: "Drop Element That Doesn't Exist", + 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}}`), @@ -90,7 +89,7 @@ func TestDropElement(t *testing.T) { }, //Errors { - description: "Error Decode", + description: "Error decode", input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`), elementToRemove: "consented_providers", output: []byte(``), @@ -98,7 +97,7 @@ func TestDropElement(t *testing.T) { errorContains: "looking for beginning of value", }, { - description: "Error Malformed", + description: "Error malformed", input: []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`), elementToRemove: "consented_providers", output: []byte(``), @@ -120,3 +119,188 @@ func TestDropElement(t *testing.T) { } } + +func TestFindElement(t *testing.T) { + tests := []struct { + description string + input []byte + elementToFind string + output []byte + elementFound bool + }{ + { + description: "Find array element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers":[1608,765,492]}}`), + elementToFind: "consented_providers", + output: []byte(`[1608,765,492]`), + elementFound: true, + }, + { + description: "Find object element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings":{"test": 1,"consented_providers": [1608,765,492]}}`), + elementToFind: "consented_providers_settings", + output: []byte(`{"test": 1,"consented_providers": [1608,765,492]}`), + elementFound: true, + }, + { + description: "Find element that doesn't exist", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings":{"test": 1,"consented_providers": [1608,765,492]}}`), + elementToFind: "test_element", + output: []byte(nil), + elementFound: false, + }, + } + + for _, tt := range tests { + exists, res, err := FindElement(tt.input, tt.elementToFind) + assert.NoError(t, err, "Error should be nil") + assert.Equal(t, tt.output, res, "Result is incorrect") + + if tt.elementFound { + assert.True(t, exists, "Element must be found") + } else { + assert.False(t, exists, "Element must not be found") + } + } +} + +func TestFindElementIndexes(t *testing.T) { + tests := []struct { + description string + input []byte + elementToFind string + startIndex int64 + endIndex int64 + found bool + errorExpected bool + errorContains string + }{ + { + description: "Find single element after another element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`), + elementToFind: "consented_providers", + startIndex: 68, + endIndex: 106, + found: true, + errorExpected: false, + errorContains: "", + }, + { + description: "Find single element before another element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`), + elementToFind: "consented_providers", + startIndex: 59, + endIndex: 97, + found: true, + errorExpected: false, + errorContains: "", + }, + { + description: "Find single element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`), + elementToFind: "consented_providers", + startIndex: 59, + endIndex: 98, + found: true, + errorExpected: false, + errorContains: "", + }, + { + description: "Find single element string", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`), + elementToFind: "consented_providers", + startIndex: 59, + endIndex: 88, + found: true, + errorExpected: false, + errorContains: "", + }, + { + description: "Find parent element between two elements", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), + elementToFind: "consented_providers_settings", + startIndex: 26, + endIndex: 109, + found: true, + errorExpected: false, + errorContains: "", + }, + { + description: "Find parent element before element", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), + elementToFind: "consented_providers_settings", + startIndex: 1, + endIndex: 84, + found: true, + errorExpected: false, + errorContains: "", + }, + { + description: "Find parent element after element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToFind: "consented_providers_settings", + startIndex: 25, + endIndex: 108, + found: true, + errorExpected: false, + errorContains: "", + }, + { + description: "Find parent element only", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToFind: "consented_providers_settings", + startIndex: 1, + endIndex: 83, + found: true, + errorExpected: false, + errorContains: "", + }, + { + description: "Find element that doesn't exist", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToFind: "test2", + startIndex: -1, + endIndex: -1, + found: false, + errorExpected: false, + errorContains: "", + }, + //Errors + { + description: "Error decode", + input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`), + elementToFind: "consented_providers", + startIndex: -1, + endIndex: -1, + found: false, + 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}}`), + elementToFind: "consented_providers", + startIndex: -1, + endIndex: -1, + found: false, + errorExpected: true, + errorContains: "invalid character", + }, + } + + for _, tt := range tests { + found, start, end, err := findElementIndexes(tt.input, tt.elementToFind) + + assert.Equal(t, tt.found, found, "Incorrect value of element existence") + + 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.startIndex, start, "Result is incorrect") + assert.Equal(t, tt.endIndex, end, "Result is incorrect") + } + + } +} From 0dbf605a64e678723b1c464c5912d2e4cbbf8f2c Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Thu, 5 Aug 2021 21:22:13 +0300 Subject: [PATCH 057/140] TheMediaGrid: Add params.keywords processing (#1913) --- adapters/bidder.go | 2 + adapters/grid/grid.go | 227 ++++++++++++- .../gridtest/exemplary/simple-banner.json | 24 +- .../gridtest/exemplary/with-keywords.json | 122 +++++++ .../exemplary/with-mixed-keywords.json | 308 ++++++++++++++++++ .../exemplary/with-siteuser-keywords.json | 120 +++++++ .../grid/gridtest/params/race/banner.json | 12 +- exchange/bidder.go | 3 + exchange/exchange.go | 1 + exchange/exchange_test.go | 80 ++--- openrtb_ext/imp_grid.go | 5 +- static/bidder-params/grid.json | 12 + util/maputil/maputil.go | 9 + util/maputil/maputil_test.go | 53 +++ 14 files changed, 929 insertions(+), 49 deletions(-) create mode 100644 adapters/grid/gridtest/exemplary/with-keywords.json create mode 100644 adapters/grid/gridtest/exemplary/with-mixed-keywords.json create mode 100644 adapters/grid/gridtest/exemplary/with-siteuser-keywords.json diff --git a/adapters/bidder.go b/adapters/bidder.go index dbe56f8eb91..e60227777bf 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -89,11 +89,13 @@ func NewBidderResponse() *BidderResponse { // openrtb_ext.ExtBidPrebid. // // TypedBid.Bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response. +// TypedBid.BidMeta will become "response.seatbid[i].bid.ext.prebid.meta" in the final OpenRTB response. // TypedBid.BidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response. // TypedBid.BidVideo will become "response.seatbid[i].bid.ext.prebid.video" in the final OpenRTB response. // TypedBid.DealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns. type TypedBid struct { Bid *openrtb2.Bid + BidMeta *openrtb_ext.ExtBidPrebidMeta BidType openrtb_ext.BidType BidVideo *openrtb_ext.ExtBidPrebidVideo DealPriority int diff --git a/adapters/grid/grid.go b/adapters/grid/grid.go index bc819fc067e..850b21c59ad 100644 --- a/adapters/grid/grid.go +++ b/adapters/grid/grid.go @@ -4,18 +4,33 @@ import ( "encoding/json" "fmt" "net/http" + "sort" + "strings" "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" + "github.com/prebid/prebid-server/util/maputil" ) type GridAdapter struct { endpoint string } +type GridBidExt struct { + Bidder ExtBidder `json:"bidder"` +} + +type ExtBidder struct { + Grid ExtBidderGrid `json:"grid"` +} + +type ExtBidderGrid struct { + DemandSource string `json:"demandSource"` +} + type ExtImpDataAdServer struct { Name string `json:"name"` AdSlot string `json:"adslot"` @@ -33,6 +48,187 @@ type ExtImp struct { Gpid string `json:"gpid,omitempty"` } +type KeywordSegment struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type KeywordsPublisherItem struct { + Name string `json:"name"` + Segments []KeywordSegment `json:"segments"` +} + +type KeywordsPublisher map[string][]KeywordsPublisherItem + +type Keywords map[string]KeywordsPublisher + +// buildConsolidatedKeywordsReqExt builds a new request.ext json incorporating request.site.keywords, request.user.keywords, +// and request.imp[0].ext.keywords, and request.ext.keywords. Invalid keywords in request.imp[0].ext.keywords are not incorporated. +// Invalid keywords in request.ext.keywords.site and request.ext.keywords.user are dropped. +func buildConsolidatedKeywordsReqExt(openRTBUser, openRTBSite string, firstImpExt, requestExt json.RawMessage) (json.RawMessage, error) { + // unmarshal ext to object map + requestExtMap := parseExtToMap(requestExt) + firstImpExtMap := parseExtToMap(firstImpExt) + // extract `keywords` field + requestExtKeywordsMap := extractKeywordsMap(requestExtMap) + firstImpExtKeywordsMap := extractBidderKeywordsMap(firstImpExtMap) + // parse + merge keywords + keywords := parseKeywordsFromMap(requestExtKeywordsMap) // request.ext.keywords + mergeKeywords(keywords, parseKeywordsFromMap(firstImpExtKeywordsMap)) // request.imp[0].ext.bidder.keywords + mergeKeywords(keywords, parseKeywordsFromOpenRTB(openRTBUser, "user")) // request.user.keywords + mergeKeywords(keywords, parseKeywordsFromOpenRTB(openRTBSite, "site")) // request.site.keywords + + // overlay site + user keywords + if site, exists := keywords["site"]; exists && len(site) > 0 { + requestExtKeywordsMap["site"] = site + } else { + delete(requestExtKeywordsMap, "site") + } + if user, exists := keywords["user"]; exists && len(user) > 0 { + requestExtKeywordsMap["user"] = user + } else { + delete(requestExtKeywordsMap, "user") + } + // reconcile keywords with request.ext + if len(requestExtKeywordsMap) > 0 { + requestExtMap["keywords"] = requestExtKeywordsMap + } else { + delete(requestExtMap, "keywords") + } + // marshal final result + if len(requestExtMap) > 0 { + return json.Marshal(requestExtMap) + } + return nil, nil +} +func parseExtToMap(ext json.RawMessage) map[string]interface{} { + var root map[string]interface{} + if err := json.Unmarshal(ext, &root); err != nil { + return make(map[string]interface{}) + } + return root +} +func extractKeywordsMap(ext map[string]interface{}) map[string]interface{} { + if keywords, exists := maputil.ReadEmbeddedMap(ext, "keywords"); exists { + return keywords + } + return make(map[string]interface{}) +} +func extractBidderKeywordsMap(ext map[string]interface{}) map[string]interface{} { + if bidder, exists := maputil.ReadEmbeddedMap(ext, "bidder"); exists { + return extractKeywordsMap(bidder) + } + return make(map[string]interface{}) +} +func parseKeywordsFromMap(extKeywords map[string]interface{}) Keywords { + keywords := make(Keywords) + for k, v := range extKeywords { + // keywords may only be provided in the site and user sections + if k != "site" && k != "user" { + continue + } + // the site or user sections must be an object + if section, ok := v.(map[string]interface{}); ok { + keywords[k] = parseKeywordsFromSection(section) + } + } + return keywords +} +func parseKeywordsFromSection(section map[string]interface{}) KeywordsPublisher { + keywordsPublishers := make(KeywordsPublisher) + for publisherKey, publisherValue := range section { + // publisher value must be a slice + publisherValueSlice, ok := publisherValue.([]interface{}) + if !ok { + continue + } + for _, publisherValueItem := range publisherValueSlice { + // item must be an object + publisherItem, ok := publisherValueItem.(map[string]interface{}) + if !ok { + continue + } + // publisher item must have a name + publisherName, ok := maputil.ReadEmbeddedString(publisherItem, "name") + if !ok { + continue + } + var segments []KeywordSegment + // extract valid segments + if segmentsSlice, exists := maputil.ReadEmbeddedSlice(publisherItem, "segments"); exists { + for _, segment := range segmentsSlice { + if segmentMap, ok := segment.(map[string]interface{}); ok { + name, hasName := maputil.ReadEmbeddedString(segmentMap, "name") + value, hasValue := maputil.ReadEmbeddedString(segmentMap, "value") + if hasName && hasValue { + segments = append(segments, KeywordSegment{Name: name, Value: value}) + } + } + } + } + // ensure consistent ordering for publisher item map + publisherItemKeys := make([]string, 0, len(publisherItem)) + for v := range publisherItem { + publisherItemKeys = append(publisherItemKeys, v) + } + sort.Strings(publisherItemKeys) + // compose compatible alternate segment format + for _, potentialSegmentName := range publisherItemKeys { + potentialSegmentValues := publisherItem[potentialSegmentName] + // values must be an array + if valuesSlice, ok := potentialSegmentValues.([]interface{}); ok { + for _, value := range valuesSlice { + if valueAsString, ok := value.(string); ok { + segments = append(segments, KeywordSegment{Name: potentialSegmentName, Value: valueAsString}) + } + } + } + } + if len(segments) > 0 { + keywordsPublishers[publisherKey] = append(keywordsPublishers[publisherKey], KeywordsPublisherItem{Name: publisherName, Segments: segments}) + } + } + } + return keywordsPublishers +} +func parseKeywordsFromOpenRTB(keywords, section string) Keywords { + keywordsSplit := strings.Split(keywords, ",") + segments := make([]KeywordSegment, 0, len(keywordsSplit)) + for _, v := range keywordsSplit { + if v != "" { + segments = append(segments, KeywordSegment{Name: "keywords", Value: v}) + } + } + if len(segments) > 0 { + return map[string]KeywordsPublisher{section: map[string][]KeywordsPublisherItem{"ortb2": {{Name: "keywords", Segments: segments}}}} + } + return make(Keywords) +} +func mergeKeywords(a, b Keywords) { + for key, values := range b { + if _, sectionExists := a[key]; !sectionExists { + a[key] = KeywordsPublisher{} + } + for publisherKey, publisherValues := range values { + a[key][publisherKey] = append(publisherValues, a[key][publisherKey]...) + } + } +} + +func setImpExtKeywords(request *openrtb2.BidRequest) error { + userKeywords := "" + if request.User != nil { + userKeywords = request.User.Keywords + } + siteKeywords := "" + if request.Site != nil { + siteKeywords = request.Site.Keywords + } + var err error + request.Ext, err = buildConsolidatedKeywordsReqExt(userKeywords, siteKeywords, request.Imp[0].Ext, request.Ext) + return err +} + func processImp(imp *openrtb2.Imp) error { // get the grid extension var ext adapters.ExtImpBidder @@ -73,12 +269,10 @@ func setImpExtData(imp openrtb2.Imp) openrtb2.Imp { func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) - // copy the request, because we are going to mutate it - requestCopy := *request // this will contain all the valid impressions var validImps []openrtb2.Imp // pre-process the imps - for _, imp := range requestCopy.Imp { + for _, imp := range request.Imp { if err := processImp(&imp); err == nil { validImps = append(validImps, setImpExtData(imp)) } else { @@ -92,9 +286,15 @@ func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapte errors = append(errors, err) return nil, errors } - requestCopy.Imp = validImps - reqJSON, err := json.Marshal(requestCopy) + if err := setImpExtKeywords(request); err != nil { + errors = append(errors, err) + return nil, errors + } + + request.Imp = validImps + + reqJSON, err := json.Marshal(request) if err != nil { errors = append(errors, err) return nil, errors @@ -138,6 +338,7 @@ func (a *GridAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq for _, sb := range bidResp.SeatBid { for i := range sb.Bid { + bidMeta, err := getBidMeta(sb.Bid[i].Ext) bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) if err != nil { return nil, []error{err} @@ -146,6 +347,7 @@ func (a *GridAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], BidType: bidType, + BidMeta: bidMeta, }) } } @@ -161,6 +363,21 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } +func getBidMeta(ext json.RawMessage) (*openrtb_ext.ExtBidPrebidMeta, error) { + var bidExt GridBidExt + + if err := json.Unmarshal(ext, &bidExt); err != nil { + return nil, err + } + var bidMeta *openrtb_ext.ExtBidPrebidMeta + if bidExt.Bidder.Grid.DemandSource != "" { + bidMeta = &openrtb_ext.ExtBidPrebidMeta{ + NetworkName: bidExt.Bidder.Grid.DemandSource, + } + } + return bidMeta, nil +} + func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/grid/gridtest/exemplary/simple-banner.json b/adapters/grid/gridtest/exemplary/simple-banner.json index 1a5ea014d0f..1161e48218a 100644 --- a/adapters/grid/gridtest/exemplary/simple-banner.json +++ b/adapters/grid/gridtest/exemplary/simple-banner.json @@ -4,6 +4,11 @@ "site": { "page": "https://good.site/url" }, + "ext": { + "keywords": { + "anyName": "anyVal" + } + }, "imp": [{ "id": "test-imp-id", "banner": { @@ -28,6 +33,11 @@ "site": { "page": "https://good.site/url" }, + "ext": { + "keywords": { + "anyName": "anyVal" + } + }, "imp": [{ "id": "test-imp-id", "banner": { @@ -59,7 +69,12 @@ "cid": "987", "crid": "12345678", "h": 250, - "w": 300 + "w": 300, + "ext": { + "bidder": { + "grid": {"demandSource":"trustx"} + } + } }] }], "cur": "USD" @@ -79,7 +94,12 @@ "cid": "987", "crid": "12345678", "w": 300, - "h": 250 + "h": 250, + "ext": { + "bidder": { + "grid": {"demandSource":"trustx"} + } + } }, "type": "banner" }] diff --git a/adapters/grid/gridtest/exemplary/with-keywords.json b/adapters/grid/gridtest/exemplary/with-keywords.json new file mode 100644 index 00000000000..a8875220618 --- /dev/null +++ b/adapters/grid/gridtest/exemplary/with-keywords.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1, + "keywords": { + "site": { + "somePublisher": [ + { + "name": "someName", + "topic": ["stress", "fear"] + } + ] + } + } + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "ext": { + "keywords": { + "site": { + "somePublisher": [ + { + "name": "someName", + "segments": [ + { "name": "topic", "value": "stress" }, + { "name": "topic", "value": "fear" } + ] + } + ] + } + } + }, + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1, + "keywords": { + "site": { + "somePublisher": [ + { + "name": "someName", + "topic": ["stress", "fear"] + } + ] + } + } + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "grid", + "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" + }] + }] +} diff --git a/adapters/grid/gridtest/exemplary/with-mixed-keywords.json b/adapters/grid/gridtest/exemplary/with-mixed-keywords.json new file mode 100644 index 00000000000..49831991831 --- /dev/null +++ b/adapters/grid/gridtest/exemplary/with-mixed-keywords.json @@ -0,0 +1,308 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "keywords": "siteKey1,siteKey2" + }, + "ext": { + "keywords": { + "stringKey": "stringVal", + "wrongKeys1": { + "someKey1": "someVal1", + "someKey2": "someVal2", + "someKey3": ["someVal31", "someVal32"], + "someKey4": { "key1": "val1", "key2": "val2" } + }, + "anotherKeys": [ + {"someKey1": "someVal1"}, + {"someKey2": "someVal2"} + ], + "site": { + "stringSiteKey": "stringSiteVal", + "wrongSiteKeys1": { + "someKey1": "someVal1", + "someKey2": "someVal2", + "someKey3": ["someVal31", "someVal32"], + "someKey4": { "key1": "val1", "key2": "val2" } + }, + "anotherSiteKeys": [ + {"someKey1": "someVal1"}, + {"someKey2": "someVal2"}, + "someStrKey", + ["someVal31", "someVal32"], + { + "name": "someName3", + "keyName": ["keyVal", { "name": "wrongKey" }], + "wrongKeyName": "stKeyVal", + "wrongKeyName2": { "name": "someName", "value": "stKeyVal" } + } + ], + "pub": [ + "k1", + "k2" + ], + "somePublisher": [ + { + "name": "someName2", + "topic": ["anyKey"] + } + ], + "formatedSitePublisher": [ + { + "name": "formatedPub2", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" } + ] + }, + { + "name": "notFormatedPub", + "topic2": ["notFormatedKw"] + } + ] + }, + "user": { + "formatedUserPublisher": [ + { + "name": "formatedPub2", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" } + ] + } + ] + } + } + }, + "user": { + "keywords": "userKey1" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1, + "keywords": { + "site": { + "somePublisher": [ + { + "name": "someName", + "topic": ["stress", "fear"] + } + ] + }, + "user": { + "formatedPublisher": [ + { + "name": "formatedPub1", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" }, + ["someKeyword"], + "stringKey" + ], + "bottom": ["bottomKey1", "bottomKey2"] + } + ] + } + } + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "ext": { + "keywords": { + "site": { + "anotherSiteKeys": [ + { + "name": "someName3", + "segments": [ + { "name": "keyName", "value": "keyVal" } + ] + } + ], + "somePublisher": [ + { + "name": "someName", + "segments": [ + { "name": "topic", "value": "stress" }, + { "name": "topic", "value": "fear" } + ] + }, + { + "name": "someName2", + "segments": [ + { "name": "topic", "value": "anyKey" } + ] + } + ], + "formatedSitePublisher": [ + { + "name": "formatedPub2", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" } + ] + }, + { + "name": "notFormatedPub", + "segments": [ + { "name": "topic2", "value": "notFormatedKw" } + ] + } + ], + "ortb2": [ + { + "name": "keywords", + "segments": [ + { "name": "keywords", "value": "siteKey1" }, + { "name": "keywords", "value": "siteKey2" } + ] + } + ] + }, + "user": { + "ortb2": [ + { + "name": "keywords", + "segments": [ + { "name": "keywords", "value": "userKey1" } + ] + } + ], + "formatedUserPublisher": [ + { + "name": "formatedPub2", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" } + ] + } + ], + "formatedPublisher": [ + { + "name": "formatedPub1", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" }, + { "name": "bottom", "value": "bottomKey1" }, + { "name": "bottom", "value": "bottomKey2" }, + { "name": "segments", "value": "stringKey" } + ] + } + ] + }, + "wrongKeys1": { + "someKey1": "someVal1", + "someKey2": "someVal2", + "someKey3": ["someVal31", "someVal32"], + "someKey4": { "key1": "val1", "key2": "val2" } + }, + "stringKey": "stringVal", + "anotherKeys": [ + {"someKey1": "someVal1"}, + {"someKey2": "someVal2"} + ] + } + }, + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "keywords": "siteKey1,siteKey2" + }, + "user": { + "keywords": "userKey1" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1, + "keywords": { + "site": { + "somePublisher": [ + { + "name": "someName", + "topic": ["stress", "fear"] + } + ] + }, + "user": { + "formatedPublisher": [ + { + "name": "formatedPub1", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" }, + ["someKeyword"], + "stringKey" + ], + "bottom": ["bottomKey1", "bottomKey2"] + } + ] + } + } + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "grid", + "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" + }] + }] +} diff --git a/adapters/grid/gridtest/exemplary/with-siteuser-keywords.json b/adapters/grid/gridtest/exemplary/with-siteuser-keywords.json new file mode 100644 index 00000000000..7a4822f8566 --- /dev/null +++ b/adapters/grid/gridtest/exemplary/with-siteuser-keywords.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "keywords": "siteKey1,siteKey2" + }, + "user": { + "keywords": "userKey1" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "ext": { + "keywords": { + "site": { + "ortb2": [ + { + "name": "keywords", + "segments": [ + { "name": "keywords", "value": "siteKey1" }, + { "name": "keywords", "value": "siteKey2" } + ] + } + ] + }, + "user": { + "ortb2": [ + { + "name": "keywords", + "segments": [ + { "name": "keywords", "value": "userKey1" } + ] + } + ] + } + } + }, + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "keywords": "siteKey1,siteKey2" + }, + "user": { + "keywords": "userKey1" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "grid", + "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" + }] + }] +} diff --git a/adapters/grid/gridtest/params/race/banner.json b/adapters/grid/gridtest/params/race/banner.json index 7e347f11b45..f64051bd563 100644 --- a/adapters/grid/gridtest/params/race/banner.json +++ b/adapters/grid/gridtest/params/race/banner.json @@ -1,4 +1,14 @@ { - "uid": 1 + "uid": 1, + "keywords": { + "site": { + "somePublisher": [ + { + "name": "someName", + "topic": ["stress", "fear"] + } + ] + } + } } diff --git a/exchange/bidder.go b/exchange/bidder.go index 83466c7d3b0..895cff936bd 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -55,6 +55,7 @@ type adaptedBidder interface { // pbsOrtbBid is a Bid returned by an adaptedBidder. // // pbsOrtbBid.bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response. +// pbsOrtbBid.bidMeta will become "response.seatbid[i].bid.ext.prebid.meta" in the final OpenRTB response. // pbsOrtbBid.bidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response. // pbsOrtbBid.bidTargets does not need to be filled out by the Bidder. It will be set later by the exchange. // pbsOrtbBid.bidVideo is optional but should be filled out by the Bidder if bidType is video. @@ -64,6 +65,7 @@ type adaptedBidder interface { // pbsOrtbBid.generatedBidID is unique bid id generated by prebid server if generate bid id option is enabled in config type pbsOrtbBid struct { bid *openrtb2.Bid + bidMeta *openrtb_ext.ExtBidPrebidMeta bidType openrtb_ext.BidType bidTargets map[string]string bidVideo *openrtb_ext.ExtBidPrebidVideo @@ -251,6 +253,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B } seatBid.bids = append(seatBid.bids, &pbsOrtbBid{ bid: bidResponse.Bids[i].Bid, + bidMeta: bidResponse.Bids[i].BidMeta, bidType: bidResponse.Bids[i].BidType, bidVideo: bidResponse.Bids[i].BidVideo, dealPriority: bidResponse.Bids[i].DealPriority, diff --git a/exchange/exchange.go b/exchange/exchange.go index b95641c82b5..1cedb5d9f43 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -932,6 +932,7 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool Events: bid.bidEvents, Targeting: bid.bidTargets, Type: bid.bidType, + Meta: bid.bidMeta, Video: bid.bidVideo, BidId: bid.generatedBidID, } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index ba2d73b4824..aa4443529ad 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2411,10 +2411,10 @@ func TestCategoryMapping(t *testing.T) { bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -2466,10 +2466,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""} + bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -2520,9 +2520,9 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -2602,9 +2602,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -2654,11 +2654,11 @@ func TestCategoryDedupe(t *testing.T) { bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_5 := pbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} selectedBids := make(map[string]int) expectedCategories := map[string]string{ @@ -2733,11 +2733,11 @@ func TestNoCategoryDedupe(t *testing.T) { bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_5 := pbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} selectedBids := make(map[string]int) expectedCategories := map[string]string{ @@ -2813,8 +2813,8 @@ func TestCategoryMappingBidderName(t *testing.T) { bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids1 := []*pbsOrtbBid{ &bid1_1, @@ -2867,8 +2867,8 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids1 := []*pbsOrtbBid{ &bid1_1, @@ -2979,7 +2979,7 @@ func TestBidRejectionErrors(t *testing.T) { innerBids := []*pbsOrtbBid{} for _, bid := range test.bids { currentBid := pbsOrtbBid{ - bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, ""} + bid, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, ""} innerBids = append(innerBids, ¤tBid) } @@ -3027,8 +3027,8 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1} bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1} - bid1_Apn1 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} - bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1 := pbsOrtbBid{&bidApn1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn2 := pbsOrtbBid{&bidApn2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBidsApn1 := []*pbsOrtbBid{ &bid1_Apn1, @@ -3107,11 +3107,11 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) 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_Apn1_1 := pbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, nil, "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, ""} + bid1_Apn2_1 := pbsOrtbBid{&bidApn2_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn2_2 := pbsOrtbBid{&bidApn2_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBidsApn1 := []*pbsOrtbBid{ &bid1_Apn1_1, @@ -3189,9 +3189,9 @@ func TestRemoveBidById(t *testing.T) { 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, ""} + bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1_3 := pbsOrtbBid{&bidApn1_3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} type aTest struct { desc string @@ -3316,7 +3316,7 @@ func TestApplyDealSupport(t *testing.T) { }, } - bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""} + bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""} bidCategory := map[string]string{ bid.bid.ID: test.targ["hb_pb_cat_dur"], } @@ -3483,7 +3483,7 @@ func TestUpdateHbPbCatDur(t *testing.T) { } for _, test := range testCases { - bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""} + bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""} bidCategory := map[string]string{ bid.bid.ID: test.targ["hb_pb_cat_dur"], } diff --git a/openrtb_ext/imp_grid.go b/openrtb_ext/imp_grid.go index d38e610d7a5..c4530426227 100644 --- a/openrtb_ext/imp_grid.go +++ b/openrtb_ext/imp_grid.go @@ -1,6 +1,9 @@ package openrtb_ext +import "encoding/json" + // ExtImpGrid defines the contract for bidrequest.imp[i].ext.grid type ExtImpGrid struct { - Uid int `json:"uid"` + Uid int `json:"uid"` + Keywords json.RawMessage `json:"keywords,omitempty"` } diff --git a/static/bidder-params/grid.json b/static/bidder-params/grid.json index 67f9b12f115..42c8858444a 100644 --- a/static/bidder-params/grid.json +++ b/static/bidder-params/grid.json @@ -7,6 +7,18 @@ "uid": { "type": "integer", "description": "An ID which identifies this placement of the impression" + }, + "keywords": { + "type": "object", + "description": "Keywords", + "properties": { + "site": { + "type": "object" + }, + "user": { + "type": "object" + } + } } }, "required": [] diff --git a/util/maputil/maputil.go b/util/maputil/maputil.go index 0d1d7dbb51c..595e93211e2 100644 --- a/util/maputil/maputil.go +++ b/util/maputil/maputil.go @@ -19,3 +19,12 @@ func ReadEmbeddedSlice(m map[string]interface{}, k string) ([]interface{}, bool) return nil, false } + +// ReadEmbeddedString reads element k from the map m as a string +func ReadEmbeddedString(m map[string]interface{}, k string) (string, bool) { + if v, ok := m[k]; ok { + vCasted, ok := v.(string) + return vCasted, ok + } + return "", false +} diff --git a/util/maputil/maputil_test.go b/util/maputil/maputil_test.go index 2e6955cec9b..abce102f5f5 100644 --- a/util/maputil/maputil_test.go +++ b/util/maputil/maputil_test.go @@ -111,3 +111,56 @@ func TestReadEmbeddedSlice(t *testing.T) { assert.Equal(t, test.expectedOK, resultOK, test.description+":ok") } } + +func TestReadEmbeddedString(t *testing.T) { + testCases := []struct { + description string + value map[string]interface{} + key string + expectedString string + expectedOK bool + }{ + { + description: "Nil", + value: nil, + key: "", + expectedString: "", + expectedOK: false, + }, + { + description: "Empty", + value: map[string]interface{}{}, + key: "foo", + expectedString: "", + expectedOK: false, + }, + { + description: "Success", + value: map[string]interface{}{"foo": "stringValue"}, + key: "foo", + expectedString: "stringValue", + expectedOK: true, + }, + { + description: "Not Found", + value: map[string]interface{}{"foo": "stringValue"}, + key: "notFound", + expectedString: "", + expectedOK: false, + }, + { + description: "Wrong Type", + value: map[string]interface{}{"foo": []interface{}{42}}, + key: "foo", + expectedString: "", + expectedOK: false, + }, + } + + for _, test := range testCases { + resultString, resultOK := ReadEmbeddedString(test.value, test.key) + + assert.Equal(t, test.expectedString, resultString, test.description+":string") + assert.Equal(t, test.expectedOK, resultOK, test.description+":ok") + } +} From 854ff54475e5da87f374e1b19d39ef32ca31e16f Mon Sep 17 00:00:00 2001 From: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Date: Thu, 5 Aug 2021 21:51:35 +0300 Subject: [PATCH 058/140] New Adapter: SmartHub (#1932) --- adapters/smarthub/params_test.go | 58 +++++ adapters/smarthub/smarthub.go | 183 ++++++++++++++++ adapters/smarthub/smarthub_test.go | 18 ++ .../smarthubtest/exemplary/banner.json | 164 ++++++++++++++ .../smarthubtest/exemplary/native.json | 160 ++++++++++++++ .../smarthubtest/exemplary/video.json | 200 ++++++++++++++++++ .../smarthubtest/params/race/banner.json | 5 + .../smarthubtest/params/race/native.json | 5 + .../smarthubtest/params/race/video.json | 5 + .../supplemental/bad-response.json | 108 ++++++++++ .../supplemental/empty-seatbid-0-bid.json | 118 +++++++++++ .../supplemental/empty-seatbid.json | 113 ++++++++++ .../invalid-ext-bidder-object.json | 49 +++++ .../supplemental/invalid-ext-object.json | 47 ++++ .../smarthubtest/supplemental/status-204.json | 102 +++++++++ .../smarthubtest/supplemental/status-400.json | 108 ++++++++++ .../smarthubtest/supplemental/status-503.json | 107 ++++++++++ .../supplemental/unexpected-status.json | 108 ++++++++++ adapters/smarthub/usersync.go | 12 ++ adapters/smarthub/usersync_test.go | 33 +++ config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_smarthub.go | 7 + static/bidder-info/smarthub.yaml | 13 ++ static/bidder-params/smarthub.json | 28 +++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 28 files changed, 1760 insertions(+) create mode 100644 adapters/smarthub/params_test.go create mode 100644 adapters/smarthub/smarthub.go create mode 100644 adapters/smarthub/smarthub_test.go create mode 100644 adapters/smarthub/smarthubtest/exemplary/banner.json create mode 100644 adapters/smarthub/smarthubtest/exemplary/native.json create mode 100644 adapters/smarthub/smarthubtest/exemplary/video.json create mode 100644 adapters/smarthub/smarthubtest/params/race/banner.json create mode 100644 adapters/smarthub/smarthubtest/params/race/native.json create mode 100644 adapters/smarthub/smarthubtest/params/race/video.json create mode 100644 adapters/smarthub/smarthubtest/supplemental/bad-response.json create mode 100644 adapters/smarthub/smarthubtest/supplemental/empty-seatbid-0-bid.json create mode 100644 adapters/smarthub/smarthubtest/supplemental/empty-seatbid.json create mode 100644 adapters/smarthub/smarthubtest/supplemental/invalid-ext-bidder-object.json create mode 100644 adapters/smarthub/smarthubtest/supplemental/invalid-ext-object.json create mode 100644 adapters/smarthub/smarthubtest/supplemental/status-204.json create mode 100644 adapters/smarthub/smarthubtest/supplemental/status-400.json create mode 100644 adapters/smarthub/smarthubtest/supplemental/status-503.json create mode 100644 adapters/smarthub/smarthubtest/supplemental/unexpected-status.json create mode 100644 adapters/smarthub/usersync.go create mode 100644 adapters/smarthub/usersync_test.go create mode 100644 openrtb_ext/imp_smarthub.go create mode 100644 static/bidder-info/smarthub.yaml create mode 100644 static/bidder-params/smarthub.json diff --git a/adapters/smarthub/params_test.go b/adapters/smarthub/params_test.go new file mode 100644 index 00000000000..7797d4a82c9 --- /dev/null +++ b/adapters/smarthub/params_test.go @@ -0,0 +1,58 @@ +package smarthub + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{"partnerName":"partnertest", "seat":"9Q20EdGxzgWdfPYShScl", "token":"eKmw6alpP3zWQhRCe3flOpz0wpuwRFjW"}`, +} + +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.BidderSmartHub, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected smarthub params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{"anyparam": "anyvalue"}`, + `{"partnerName":"partnertest"}`, + `{"seat":"9Q20EdGxzgWdfPYShScl"}`, + `{"token":"Y9Evrh40ejsrCR4EtidUt1cSxhJsz8X1"}`, + `{"seat":"9Q20EdGxzgWdfPYShScl", "token":"alNYtemWggraDVbhJrsOs9pXc3Eld32E"}`, + `{"partnerName":"partnertest", "token":"LNywdP2ebX5iETF8gvBeEoB6Cam64eeq"}`, + `{"partnerName":"partnertest", "seat":"9Q20EdGxzgWdfPYShScl"}`, + `{"partnerName":"", "seat":"", "token":""}`, + `{"partnerName":"", "seat":"9Q20EdGxzgWdfPYShScl", "token":"alNYtemWggraDVbhJrsOs9pXc3Eld32E"}`, + `{"partnerName":"partnertest", "seat":"9Q20EdGxzgWdfPYShScl", "token":""}`, + `{"partnerName":"partnertest", "seat":"", "token":"alNYtemWggraDVbhJrsOs9pXc3Eld32E"}`, +} + +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.BidderSmartHub, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/smarthub/smarthub.go b/adapters/smarthub/smarthub.go new file mode 100644 index 00000000000..4a374fa208c --- /dev/null +++ b/adapters/smarthub/smarthub.go @@ -0,0 +1,183 @@ +package smarthub + +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" +) + +const ( + ADAPTER_VER = "1.0.0" +) + +type adapter struct { + endpoint template.Template +} + +type bidExt struct { + MediaType string `json:"mediaType"` +} + +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) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtSmartHub, 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 smarthubExt openrtb_ext.ExtSmartHub + if err := json.Unmarshal(bidderExt.Bidder, &smarthubExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error while unmarshaling bidder extension", + } + } + + return &smarthubExt, nil +} + +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtSmartHub) (string, error) { + endpointParams := macros.EndpointTemplateParams{ + Host: params.PartnerName, + AccountID: params.Seat, + SourceId: params.Token, + } + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + []*adapters.RequestData, + []error, +) { + var smarthubExt *openrtb_ext.ExtSmartHub + smarthubExt, err := a.getImpressionExt(&(openRTBRequest.Imp[0])) + if err != nil { + return nil, []error{err} + } + + url, err := a.buildEndpointURL(smarthubExt) + if err != nil { + return nil, []error{err} + } + + 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") + headers.Add("Prebid-Adapter-Ver", ADAPTER_VER) + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: url, + Headers: headers, + }}, nil +} + +func (a *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + *adapters.BidderResponse, + []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: "Array SeatBid cannot be empty", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + bids := bidResp.SeatBid[0].Bid + + if len(bids) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Array SeatBid[0].Bid cannot be empty", + }} + } + + 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: "Field BidExt is required", + }} + } + + 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/smarthub/smarthub_test.go b/adapters/smarthub/smarthub_test.go new file mode 100644 index 00000000000..9a715208e72 --- /dev/null +++ b/adapters/smarthub/smarthub_test.go @@ -0,0 +1,18 @@ +package smarthub + +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.BidderSmartHub, config.Adapter{ + Endpoint: "http://{{.Host}}-prebid.smart-hub.io/?seat={{.AccountID}}&token={{.SourceId}}"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "smarthubtest", bidder) +} diff --git a/adapters/smarthub/smarthubtest/exemplary/banner.json b/adapters/smarthub/smarthubtest/exemplary/banner.json new file mode 100644 index 00000000000..a8b231b0ec5 --- /dev/null +++ b/adapters/smarthub/smarthubtest/exemplary/banner.json @@ -0,0 +1,164 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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/smarthub/smarthubtest/exemplary/native.json b/adapters/smarthub/smarthubtest/exemplary/native.json new file mode 100644 index 00000000000..60c9472ab78 --- /dev/null +++ b/adapters/smarthub/smarthubtest/exemplary/native.json @@ -0,0 +1,160 @@ +{ + "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": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "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": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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/smarthub/smarthubtest/exemplary/video.json b/adapters/smarthub/smarthubtest/exemplary/video.json new file mode 100644 index 00000000000..f64e4fe1fd4 --- /dev/null +++ b/adapters/smarthub/smarthubtest/exemplary/video.json @@ -0,0 +1,200 @@ +{ + "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": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "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": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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/smarthub/smarthubtest/params/race/banner.json b/adapters/smarthub/smarthubtest/params/race/banner.json new file mode 100644 index 00000000000..8c05702c6b2 --- /dev/null +++ b/adapters/smarthub/smarthubtest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" +} diff --git a/adapters/smarthub/smarthubtest/params/race/native.json b/adapters/smarthub/smarthubtest/params/race/native.json new file mode 100644 index 00000000000..8c05702c6b2 --- /dev/null +++ b/adapters/smarthub/smarthubtest/params/race/native.json @@ -0,0 +1,5 @@ +{ + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" +} diff --git a/adapters/smarthub/smarthubtest/params/race/video.json b/adapters/smarthub/smarthubtest/params/race/video.json new file mode 100644 index 00000000000..8c05702c6b2 --- /dev/null +++ b/adapters/smarthub/smarthubtest/params/race/video.json @@ -0,0 +1,5 @@ +{ + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" +} diff --git a/adapters/smarthub/smarthubtest/supplemental/bad-response.json b/adapters/smarthub/smarthubtest/supplemental/bad-response.json new file mode 100644 index 00000000000..423d144b979 --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/bad-response.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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/smarthub/smarthubtest/supplemental/empty-seatbid-0-bid.json b/adapters/smarthub/smarthubtest/supplemental/empty-seatbid-0-bid.json new file mode 100644 index 00000000000..d40787a707f --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/empty-seatbid-0-bid.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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": [ + { + "bid": [], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Array SeatBid[0].Bid cannot be empty", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/smarthub/smarthubtest/supplemental/empty-seatbid.json b/adapters/smarthub/smarthubtest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..1789e71f785 --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/empty-seatbid.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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": "Array SeatBid cannot be empty", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/smarthub/smarthubtest/supplemental/invalid-ext-bidder-object.json b/adapters/smarthub/smarthubtest/supplemental/invalid-ext-bidder-object.json new file mode 100644 index 00000000000..f8e92d340cd --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/invalid-ext-bidder-object.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": [] + } + } + ], + "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": [], + "expectedMakeRequestsErrors": [ + { + "value": "Error while unmarshaling bidder extension", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smarthub/smarthubtest/supplemental/invalid-ext-object.json b/adapters/smarthub/smarthubtest/supplemental/invalid-ext-object.json new file mode 100644 index 00000000000..f84f4fa4cbb --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/invalid-ext-object.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": "" + } + ], + "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": [], + "expectedMakeRequestsErrors": [ + { + "value": "Bidder extension not provided or can't be unmarshalled", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smarthub/smarthubtest/supplemental/status-204.json b/adapters/smarthub/smarthubtest/supplemental/status-204.json new file mode 100644 index 00000000000..a72264d0207 --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/status-204.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": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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/smarthub/smarthubtest/supplemental/status-400.json b/adapters/smarthub/smarthubtest/supplemental/status-400.json new file mode 100644 index 00000000000..7af75b06c5e --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/status-400.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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/smarthub/smarthubtest/supplemental/status-503.json b/adapters/smarthub/smarthubtest/supplemental/status-503.json new file mode 100644 index 00000000000..d2cc83dd54f --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/status-503.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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/smarthub/smarthubtest/supplemental/unexpected-status.json b/adapters/smarthub/smarthubtest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..b2f1c76e143 --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/unexpected-status.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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/smarthub/usersync.go b/adapters/smarthub/usersync.go new file mode 100644 index 00000000000..3cd4de2a335 --- /dev/null +++ b/adapters/smarthub/usersync.go @@ -0,0 +1,12 @@ +package smarthub + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSmartHubSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("smarthub", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/smarthub/usersync_test.go b/adapters/smarthub/usersync_test.go new file mode 100644 index 00000000000..b834b21b210 --- /dev/null +++ b/adapters/smarthub/usersync_test.go @@ -0,0 +1,33 @@ +package smarthub + +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 TestNewSmartHubSyncer(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 := NewSmartHubSyncer(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/config/config.go b/config/config.go index b1d0553f38c..c5fc50a60b7 100644 --- a/config/config.go +++ b/config/config.go @@ -674,6 +674,7 @@ func (cfg *Configuration) setDerivedDefaults() { // openrtb_ext.BidderRubicon doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSharethrough, "https://match.sharethrough.com/FGMrCMMc/v1?redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsharethrough%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") 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") + // openrtb_ext.BidderSmartHub doesn't have a good default. 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") @@ -950,6 +951,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.silvermob.endpoint", "http://{{.Host}}.silvermob.com/marketplace/api/dsp/bid/{{.ZoneID}}") v.SetDefault("adapters.smaato.endpoint", "https://prebid.ad.smaato.net/oapi/prebid") v.SetDefault("adapters.smartadserver.endpoint", "https://ssb-global.smartadserver.com") + v.SetDefault("adapters.smarthub.endpoint", "http://{{.Host}}-prebid.smart-hub.io/?seat={{.AccountID}}&token={{.SourceId}}") 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index e1dbc3509b2..7a61a914357 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -97,6 +97,7 @@ import ( "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/smarthub" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/smartyads" "github.com/prebid/prebid-server/adapters/smilewanted" @@ -224,6 +225,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSilverMob: silvermob.Builder, openrtb_ext.BidderSmaato: smaato.Builder, openrtb_ext.BidderSmartAdserver: smartadserver.Builder, + openrtb_ext.BidderSmartHub: smarthub.Builder, openrtb_ext.BidderSmartRTB: smartrtb.Builder, openrtb_ext.BidderSmartyAds: smartyads.Builder, openrtb_ext.BidderSmileWanted: smilewanted.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 2a08fd19eb2..8e74fcc6db8 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -169,6 +169,7 @@ const ( BidderSilverMob BidderName = "silvermob" BidderSmaato BidderName = "smaato" BidderSmartAdserver BidderName = "smartadserver" + BidderSmartHub BidderName = "smarthub" BidderSmartRTB BidderName = "smartrtb" BidderSmartyAds BidderName = "smartyads" BidderSmileWanted BidderName = "smilewanted" @@ -295,6 +296,7 @@ func CoreBidderNames() []BidderName { BidderSilverMob, BidderSmaato, BidderSmartAdserver, + BidderSmartHub, BidderSmartRTB, BidderSmartyAds, BidderSmileWanted, diff --git a/openrtb_ext/imp_smarthub.go b/openrtb_ext/imp_smarthub.go new file mode 100644 index 00000000000..37234baf169 --- /dev/null +++ b/openrtb_ext/imp_smarthub.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtSmartHub struct { + PartnerName string `json:"partnerName,omitempty"` + Seat string `json:"seat,omitempty"` + Token string `json:"token,omitempty"` +} diff --git a/static/bidder-info/smarthub.yaml b/static/bidder-info/smarthub.yaml new file mode 100644 index 00000000000..bfee9490840 --- /dev/null +++ b/static/bidder-info/smarthub.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "support@smart-hub.io" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/smarthub.json b/static/bidder-params/smarthub.json new file mode 100644 index 00000000000..dd9888fa937 --- /dev/null +++ b/static/bidder-params/smarthub.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "SmartHub Adapter Params", + "description": "A schema which validates params accepted by the SmartHub adapter", + "type": "object", + "properties": { + "partnerName": { + "type": "string", + "description": "SmartHub unique partner name", + "minLength": 1 + }, + "seat": { + "type": "string", + "description": "Seat", + "minLength": 1 + }, + "token": { + "type": "string", + "description": "Token", + "minLength": 1 + } + }, + "required": [ + "partnerName", + "seat", + "token" + ] +} \ No newline at end of file diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 8275869f5b2..87f8cf258f0 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -76,6 +76,7 @@ import ( "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/smarthub" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/smartyads" "github.com/prebid/prebid-server/adapters/smilewanted" @@ -183,6 +184,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSonobi, sonobi.NewSonobiSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSovrn, sovrn.NewSovrnSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartAdserver, smartadserver.NewSmartadserverSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartHub, smarthub.NewSmartHubSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartyAds, smartyads.NewSmartyAdsSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmileWanted, smilewanted.NewSmileWantedSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 342d2ae81b9..7eacd086ac3 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -85,6 +85,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderRubicon): syncConfig, string(openrtb_ext.BidderSharethrough): syncConfig, string(openrtb_ext.BidderSmartAdserver): syncConfig, + string(openrtb_ext.BidderSmartHub): syncConfig, string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSmartyAds): syncConfig, string(openrtb_ext.BidderSmileWanted): syncConfig, From 39e8a29469b21c176859eac8597c5bf00e1b8ff4 Mon Sep 17 00:00:00 2001 From: IOTiagoFaria <76956619+IOTiagoFaria@users.noreply.github.com> Date: Mon, 9 Aug 2021 15:15:37 +0100 Subject: [PATCH 059/140] InteractiveOffers: New Endpoint Address (#1950) --- adapters/interactiveoffers/interactiveoffers_test.go | 2 +- .../interactiveofferstest/exemplary/goodmultiplebidrequest.json | 2 +- .../interactiveofferstest/exemplary/goodsinglebidrequest.json | 2 +- .../interactiveofferstest/supplemental/204.json | 2 +- .../interactiveofferstest/supplemental/400.json | 2 +- .../interactiveofferstest/supplemental/not200.json | 2 +- .../interactiveofferstest/supplemental/wrongjsonresponse.json | 2 +- config/config.go | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/adapters/interactiveoffers/interactiveoffers_test.go b/adapters/interactiveoffers/interactiveoffers_test.go index 5746f123b41..607bd6d2925 100644 --- a/adapters/interactiveoffers/interactiveoffers_test.go +++ b/adapters/interactiveoffers/interactiveoffers_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderInteractiveoffers, config.Adapter{ - Endpoint: "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04"}) + Endpoint: "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json index 946289e5401..f42475471a4 100644 --- a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json +++ b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json @@ -35,7 +35,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", "body": { "id": "test-request-id", "site": { diff --git a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json index 2f49d5451c8..ec670d74beb 100644 --- a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json +++ b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json @@ -22,7 +22,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", "body": { "id": "test-request-id", "site": { diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json index 0d56311a188..9dc9a1615d1 100644 --- a/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json +++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json @@ -22,7 +22,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", "body": { "id": "test-request-id", "site": { diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json index 9aaf12cb239..767c25bc2f4 100644 --- a/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json +++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json @@ -22,7 +22,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", "body": { "id": "test-request-id", "site": { diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json index 222be912a92..25d8b21cd62 100644 --- a/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json +++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json @@ -22,7 +22,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", "body": { "id": "test-request-id", "site": { diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json index 7ae16d4a95a..b3f48c1f309 100644 --- a/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json +++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json @@ -22,7 +22,7 @@ "httpCalls": [{ "expectedRequest": { - "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", "body": { "id": "test-request-id", "site": { diff --git a/config/config.go b/config/config.go index c5fc50a60b7..22cbac05fba 100644 --- a/config/config.go +++ b/config/config.go @@ -911,7 +911,7 @@ func SetupViper(v *viper.Viper, filename string) { 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.interactiveoffers.endpoint", "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04") v.SetDefault("adapters.ix.disabled", true) v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost") v.SetDefault("adapters.kayzen.endpoint", "https://bids-{{.ZoneID}}.bidder.kayzen.io/?exchange={{.AccountID}}") From 914a1fd706187a83ae9250996fadfccd736b0f88 Mon Sep 17 00:00:00 2001 From: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Date: Wed, 11 Aug 2021 14:41:08 +0300 Subject: [PATCH 060/140] TheMediaGrid: Additional tests for Keywords cases (#1952) --- .../gridtest/exemplary/with-ext-keywords.json | 216 ++++++++++++++++++ .../exemplary/with-site-keywords.json | 104 +++++++++ .../exemplary/with-user-keywords.json | 109 +++++++++ 3 files changed, 429 insertions(+) create mode 100644 adapters/grid/gridtest/exemplary/with-ext-keywords.json create mode 100644 adapters/grid/gridtest/exemplary/with-site-keywords.json create mode 100644 adapters/grid/gridtest/exemplary/with-user-keywords.json diff --git a/adapters/grid/gridtest/exemplary/with-ext-keywords.json b/adapters/grid/gridtest/exemplary/with-ext-keywords.json new file mode 100644 index 00000000000..e0d0563c241 --- /dev/null +++ b/adapters/grid/gridtest/exemplary/with-ext-keywords.json @@ -0,0 +1,216 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "ext": { + "keywords": { + "stringKey": "stringVal", + "wrongKeys1": { + "someKey1": "someVal1", + "someKey2": "someVal2", + "someKey3": ["someVal31", "someVal32"], + "someKey4": { "key1": "val1", "key2": "val2" } + }, + "anotherKeys": [ + {"someKey1": "someVal1"}, + {"someKey2": "someVal2"} + ], + "site": { + "stringSiteKey": "stringSiteVal", + "wrongSiteKeys1": { + "someKey1": "someVal1", + "someKey2": "someVal2", + "someKey3": ["someVal31", "someVal32"], + "someKey4": { "key1": "val1", "key2": "val2" } + }, + "anotherSiteKeys": [ + {"someKey1": "someVal1"}, + {"someKey2": "someVal2"}, + "someStrKey", + ["someVal31", "someVal32"], + { + "name": "someName3", + "keyName": ["keyVal", { "name": "wrongKey" }], + "wrongKeyName": "stKeyVal", + "wrongKeyName2": { "name": "someName", "value": "stKeyVal" } + } + ], + "pub": [ + "k1", + "k2" + ], + "somePublisher": [ + { + "name": "someName2", + "topic": ["anyKey"] + } + ], + "formatedSitePublisher": [ + { + "name": "formatedPub2", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" } + ] + }, + { + "name": "notFormatedPub", + "topic2": ["notFormatedKw"] + } + ] + }, + "user": { + "formatedUserPublisher": [ + { + "name": "formatedPub2", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" } + ] + } + ] + } + } + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "ext": { + "keywords": { + "site": { + "anotherSiteKeys": [ + { + "name": "someName3", + "segments": [ + { "name": "keyName", "value": "keyVal" } + ] + } + ], + "somePublisher": [ + { + "name": "someName2", + "segments": [ + { "name": "topic", "value": "anyKey" } + ] + } + ], + "formatedSitePublisher": [ + { + "name": "formatedPub2", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" } + ] + }, + { + "name": "notFormatedPub", + "segments": [ + { "name": "topic2", "value": "notFormatedKw" } + ] + } + ] + }, + "user": { + "formatedUserPublisher": [ + { + "name": "formatedPub2", + "segments": [ + { "name": "segName1", "value": "segVal1" }, + { "name": "segName2", "value": "segVal2" } + ] + } + ] + }, + "wrongKeys1": { + "someKey1": "someVal1", + "someKey2": "someVal2", + "someKey3": ["someVal31", "someVal32"], + "someKey4": { "key1": "val1", "key2": "val2" } + }, + "stringKey": "stringVal", + "anotherKeys": [ + {"someKey1": "someVal1"}, + {"someKey2": "someVal2"} + ] + } + }, + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "grid", + "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" + }] + }] +} diff --git a/adapters/grid/gridtest/exemplary/with-site-keywords.json b/adapters/grid/gridtest/exemplary/with-site-keywords.json new file mode 100644 index 00000000000..bbdf830a42d --- /dev/null +++ b/adapters/grid/gridtest/exemplary/with-site-keywords.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "keywords": "siteKey1,siteKey2" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "ext": { + "keywords": { + "site": { + "ortb2": [ + { + "name": "keywords", + "segments": [ + { "name": "keywords", "value": "siteKey1" }, + { "name": "keywords", "value": "siteKey2" } + ] + } + ] + } + } + }, + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "keywords": "siteKey1,siteKey2" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "grid", + "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" + }] + }] +} diff --git a/adapters/grid/gridtest/exemplary/with-user-keywords.json b/adapters/grid/gridtest/exemplary/with-user-keywords.json new file mode 100644 index 00000000000..dbb8a7fe9d2 --- /dev/null +++ b/adapters/grid/gridtest/exemplary/with-user-keywords.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "user": { + "keywords": "userKey1,userKey2,userKey3" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "ext": { + "keywords": { + "user": { + "ortb2": [ + { + "name": "keywords", + "segments": [ + { "name": "keywords", "value": "userKey1" }, + { "name": "keywords", "value": "userKey2" }, + { "name": "keywords", "value": "userKey3" } + ] + } + ] + } + } + }, + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "user": { + "keywords": "userKey1,userKey2,userKey3" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "uid": 1 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "grid", + "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" + }] + }] +} From 2e3376aa57689eeede0ec95c7acf0a60ac7f9fe9 Mon Sep 17 00:00:00 2001 From: susyt Date: Wed, 11 Aug 2021 10:20:37 -0700 Subject: [PATCH 061/140] GumGum: adds slot param (#1949) --- adapters/gumgum/gumgum.go | 34 ++++++ .../supplemental/banner-with-slot-param.json | 107 ++++++++++++++++++ adapters/gumgum/params_test.go | 3 + openrtb_ext/imp_gumgum.go | 8 ++ static/bidder-params/gumgum.json | 4 + 5 files changed, 156 insertions(+) create mode 100644 adapters/gumgum/gumgumtest/supplemental/banner-with-slot-param.json diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go index 00927b9bf80..27533127134 100644 --- a/adapters/gumgum/gumgum.go +++ b/adapters/gumgum/gumgum.go @@ -146,6 +146,16 @@ func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) { format := bannerCopy.Format[0] bannerCopy.W = &(format.W) bannerCopy.H = &(format.H) + + if gumgumExt.Slot != 0 { + var err error + bannerExt := getBiggerFormat(bannerCopy.Format, gumgumExt.Slot) + bannerCopy.Ext, err = json.Marshal(&bannerExt) + if err != nil { + return nil, err + } + } + imp.Banner = &bannerCopy } @@ -169,6 +179,30 @@ func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) { return &gumgumExt, nil } +func getBiggerFormat(formatList []openrtb2.Format, slot float64) openrtb_ext.ExtImpGumGumBanner { + maxw := int64(0) + maxh := int64(0) + greatestVal := int64(0) + for _, size := range formatList { + var biggerSide int64 + if size.W > size.H { + biggerSide = size.W + } else { + biggerSide = size.H + } + + if biggerSide > greatestVal || (biggerSide == greatestVal && size.W >= maxw && size.H >= maxh) { + greatestVal = biggerSide + maxh = size.H + maxw = size.W + } + } + + bannerExt := openrtb_ext.ExtImpGumGumBanner{Si: slot, MaxW: float64(maxw), MaxH: float64(maxh)} + + return bannerExt +} + func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID && imp.Banner != nil { diff --git a/adapters/gumgum/gumgumtest/supplemental/banner-with-slot-param.json b/adapters/gumgum/gumgumtest/supplemental/banner-with-slot-param.json new file mode 100644 index 00000000000..d50ab6a74c9 --- /dev/null +++ b/adapters/gumgum/gumgumtest/supplemental/banner-with-slot-param.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "zone": "dc9d6be1", + "slot": 12345678 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://g2.gumgum.com/providers/prbds2s/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ], + "w": 300, + "h": 250, + "ext": { + "si": 12345678, + "maxw": 300, + "maxh": 300 + } + }, + "ext": { + "bidder": { + "zone": "dc9d6be1", + "slot": 12345678 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "2068416", + "adm": "some-test-ad", + "adid": "2068416", + "price": 5, + "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05", + "impid": "test-imp-id", + "cid": "4747" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "2068416", + "adm": "some-test-ad", + "adid": "2068416", + "price": 5, + "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05", + "impid": "test-imp-id", + "cid": "4747" + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/gumgum/params_test.go b/adapters/gumgum/params_test.go index 726b6961945..a578ce65762 100644 --- a/adapters/gumgum/params_test.go +++ b/adapters/gumgum/params_test.go @@ -36,6 +36,8 @@ var validParams = []string{ `{"zone":"dc9d6be1"}`, `{"pubId":12345678}`, `{"zone":"dc9d6be1", "pubId":12345678}`, + `{"zone":"dc9d6be1", "slot":1234567}`, + `{"pubId":12345678, "slot":1234567}`, `{"pubId":12345678, "irisid": "iris_6f9285823a48bne5"}`, `{"zone":"dc9d6be1", "irisid": "iris_6f9285823a48bne5"}`, `{"zone":"dc9d6be1", "pubId":12345678, "irisid": "iris_6f9285823a48bne5"}`, @@ -55,6 +57,7 @@ var invalidParams = []string{ `{"zone": true}`, `{"placementId": 1, "zone":"1234567"}`, `{"pubId":"123456"}`, + `{"slot":123456}`, `{"zone":"1234567", "irisid": ""}`, `{"zone":"1234567", "irisid": 1234}`, `{"irisid": "iris_6f9285823a48bne5"}`, diff --git a/openrtb_ext/imp_gumgum.go b/openrtb_ext/imp_gumgum.go index 56b000a6acf..8e0d7dd8983 100644 --- a/openrtb_ext/imp_gumgum.go +++ b/openrtb_ext/imp_gumgum.go @@ -6,9 +6,17 @@ type ExtImpGumGum struct { Zone string `json:"zone,omitempty"` PubID float64 `json:"pubId,omitempty"` IrisID string `json:"irisid,omitempty"` + Slot float64 `json:"slot,omitempty"` } // ExtImpGumGumVideo defines the contract for bidresponse.seatbid.bid[i].ext.gumgum.video type ExtImpGumGumVideo struct { IrisID string `json:"irisid,omitempty"` } + +// ExtImpGumGumBanner defines the contract for bidresponse.seatbid.bid[i].ext.gumgum.banner +type ExtImpGumGumBanner struct { + Si float64 `json:"si,omitempty"` + MaxW float64 `json:"maxw,omitempty"` + MaxH float64 `json:"maxh,omitempty"` +} diff --git a/static/bidder-params/gumgum.json b/static/bidder-params/gumgum.json index 8c8677d9407..95f05e7d517 100644 --- a/static/bidder-params/gumgum.json +++ b/static/bidder-params/gumgum.json @@ -16,6 +16,10 @@ "irisid": { "type": "string", "description": "A hashed IRIS.TV Content ID" + }, + "slot": { + "type": "integer", + "description": "A slot id used to identify a slot placement mapped to a GumGum zone or publisher" } }, "anyOf": [ From 7a9bc316a28078c3c5749d717bfb112935ce59fd Mon Sep 17 00:00:00 2001 From: Daniel Lawrence Date: Wed, 11 Aug 2021 10:43:49 -0700 Subject: [PATCH 062/140] updating the inmobi usersync url (#1956) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 22cbac05fba..865c422ffb8 100644 --- a/config/config.go +++ b/config/config.go @@ -647,7 +647,7 @@ 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.BidderInMobi, "https://sync.inmobi.com/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+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=194962&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") From 744bf42e451d2d91bfadc029c47439505075cfe1 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Wed, 18 Aug 2021 09:57:31 -0400 Subject: [PATCH 063/140] Remove time.Now() as the first parameter of NewRates() (#1953) --- currency/aggregate_conversions_test.go | 5 +- currency/rate_converter_test.go | 6 -- currency/rates.go | 26 +------- currency/rates_test.go | 87 ++++++++------------------ endpoints/openrtb2/auction_test.go | 8 +-- exchange/bidder_test.go | 14 ----- exchange/exchange.go | 4 +- 7 files changed, 34 insertions(+), 116 deletions(-) diff --git a/currency/aggregate_conversions_test.go b/currency/aggregate_conversions_test.go index 773a596c28c..d2ac961a8a7 100644 --- a/currency/aggregate_conversions_test.go +++ b/currency/aggregate_conversions_test.go @@ -3,7 +3,6 @@ package currency import ( "errors" "testing" - "time" "github.com/stretchr/testify/assert" ) @@ -11,14 +10,14 @@ import ( func TestGroupedGetRate(t *testing.T) { // Setup: - customRates := NewRates(time.Now(), map[string]map[string]float64{ + customRates := NewRates(map[string]map[string]float64{ "USD": { "GBP": 3.00, "EUR": 2.00, }, }) - pbsRates := NewRates(time.Now(), map[string]map[string]float64{ + pbsRates := NewRates(map[string]map[string]float64{ "USD": { "GBP": 4.00, "MXN": 10.00, diff --git a/currency/rate_converter_test.go b/currency/rate_converter_test.go index e1d6741dff2..59cb0f8c4ef 100644 --- a/currency/rate_converter_test.go +++ b/currency/rate_converter_test.go @@ -54,7 +54,6 @@ func TestReadWriteRates(t *testing.T) { wantUpdateErr bool wantConstantRates bool wantLastUpdated time.Time - wantDataAsOf time.Time wantConversions map[string]map[string]float64 }{ { @@ -63,7 +62,6 @@ func TestReadWriteRates(t *testing.T) { giveMockResponse: getMockRates(), giveMockStatus: 200, wantLastUpdated: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), - wantDataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), wantConversions: map[string]map[string]float64{"USD": {"GBP": 0.77208}, "GBP": {"USD": 1.2952}}, }, { @@ -72,7 +70,6 @@ func TestReadWriteRates(t *testing.T) { giveMockResponse: []byte("{}"), giveMockStatus: 200, wantLastUpdated: time.Date(2018, time.September, 12, 30, 0, 0, 0, time.UTC), - wantDataAsOf: time.Time{}, wantConversions: nil, }, { @@ -143,7 +140,6 @@ func TestReadWriteRates(t *testing.T) { } else { rates := currencyConverter.Rates().(*Rates) assert.Equal(t, tt.wantConversions, (*rates).Conversions, tt.description) - assert.Equal(t, tt.wantDataAsOf, (*rates).DataAsOf, tt.description) } lastUpdated := currencyConverter.LastUpdated() @@ -169,7 +165,6 @@ func TestRateStaleness(t *testing.T) { defer mockedHttpServer.Close() expectedRates := &Rates{ - DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), Conversions: map[string]map[string]float64{ "USD": { "GBP": 0.77208, @@ -257,7 +252,6 @@ func TestRatesAreNeverConsideredStale(t *testing.T) { defer mockedHttpServer.Close() expectedRates := &Rates{ - DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), Conversions: map[string]map[string]float64{ "USD": { "GBP": 0.77208, diff --git a/currency/rates.go b/currency/rates.go index b9cb0201b38..f1cc19fcccb 100644 --- a/currency/rates.go +++ b/currency/rates.go @@ -1,9 +1,7 @@ package currency import ( - "encoding/json" "errors" - "time" "golang.org/x/text/currency" ) @@ -12,38 +10,16 @@ import ( // note that `DataAsOfRaw` field is needed when parsing remote JSON as the date format if not standard and requires // custom parsing to be properly set as Golang time.Time type Rates struct { - DataAsOf time.Time `json:"dataAsOf"` Conversions map[string]map[string]float64 `json:"conversions"` } // NewRates creates a new Rates object holding currencies rates -func NewRates(dataAsOf time.Time, conversions map[string]map[string]float64) *Rates { +func NewRates(conversions map[string]map[string]float64) *Rates { return &Rates{ - DataAsOf: dataAsOf, Conversions: conversions, } } -// UnmarshalJSON unmarshal raw JSON bytes to Rates object -func (r *Rates) UnmarshalJSON(b []byte) error { - c := &struct { - DataAsOf string `json:"dataAsOf"` - Conversions map[string]map[string]float64 `json:"conversions"` - }{} - if err := json.Unmarshal(b, &c); err != nil { - return err - } - - r.Conversions = c.Conversions - - layout := "2006-01-02" - if date, err := time.Parse(layout, c.DataAsOf); err == nil { - r.DataAsOf = date - } - - return nil -} - // 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. diff --git a/currency/rates_test.go b/currency/rates_test.go index bca332e0858..23226dce8fb 100644 --- a/currency/rates_test.go +++ b/currency/rates_test.go @@ -2,48 +2,31 @@ package currency import ( "encoding/json" + "errors" "testing" - "time" "github.com/stretchr/testify/assert" ) func TestUnMarshallRates(t *testing.T) { - // Setup: testCases := []struct { + desc string ratesJSON string expectedRates Rates expectsError bool + expectedError error }{ { - ratesJSON: `{ - "dataAsOf":"2018-09-12", - "conversions":{ - "USD":{ - "GBP":0.7662523901 - }, - "GBP":{ - "USD":1.3050530256 - } - } - }`, - expectedRates: Rates{ - DataAsOf: time.Date(2018, time.September, 12, 0, 0, 0, 0, time.UTC), - Conversions: map[string]map[string]float64{ - "USD": { - "GBP": 0.7662523901, - }, - "GBP": { - "USD": 1.3050530256, - }, - }, - }, - expectsError: false, + desc: "malformed JSON object, return error", + ratesJSON: `malformed`, + expectedRates: Rates{}, + expectsError: true, + expectedError: errors.New("invalid character 'm' looking for beginning of value"), }, { + desc: "Valid JSON field defining valid conversion object. Expect no error", ratesJSON: `{ - "dataAsOf":"", "conversions":{ "USD":{ "GBP":0.7662523901 @@ -54,7 +37,6 @@ func TestUnMarshallRates(t *testing.T) { } }`, expectedRates: Rates{ - DataAsOf: time.Time{}, Conversions: map[string]map[string]float64{ "USD": { "GBP": 0.7662523901, @@ -64,47 +46,25 @@ func TestUnMarshallRates(t *testing.T) { }, }, }, - expectsError: false, + expectsError: false, + expectedError: nil, }, { + desc: "Valid JSON field defines a conversions map with repeated entries, expect error", ratesJSON: `{ - "dataAsOf":"blabla", "conversions":{ "USD":{ - "GBP":0.7662523901 - }, - "GBP":{ - "USD":1.3050530256 - } - } - }`, - expectedRates: Rates{ - DataAsOf: time.Time{}, - Conversions: map[string]map[string]float64{ - "USD": { - "GBP": 0.7662523901, - }, - "GBP": { - "USD": 1.3050530256, + "GBP":0.7662523901, + "MXN":20.00 }, - }, - }, - expectsError: false, - }, - { - ratesJSON: `{ - "dataAsOf":"blabla", - "conversions":{ "USD":{ - "GBP":0.7662523901, + "GBP":0.7662523901 }, - "GBP":{ - "USD":1.3050530256, - } } }`, expectedRates: Rates{}, expectsError: true, + expectedError: errors.New("invalid character '}' looking for beginning of object key string"), }, } @@ -114,15 +74,18 @@ func TestUnMarshallRates(t *testing.T) { err := json.Unmarshal([]byte(tc.ratesJSON), &updatedRates) // Verify: - assert.Equal(t, err != nil, tc.expectsError) - assert.Equal(t, tc.expectedRates, updatedRates, "Rates weren't the expected ones") + assert.Equal(t, err != nil, tc.expectsError, tc.desc) + if tc.expectsError { + assert.Equal(t, err.Error(), tc.expectedError.Error(), tc.desc) + } + assert.Equal(t, tc.expectedRates, updatedRates, tc.desc) } } func TestGetRate(t *testing.T) { // Setup: - rates := NewRates(time.Now(), map[string]map[string]float64{ + rates := NewRates(map[string]map[string]float64{ "USD": { "GBP": 0.77208, }, @@ -165,7 +128,7 @@ func TestGetRate(t *testing.T) { func TestGetRate_ReverseConversion(t *testing.T) { // Setup: - rates := NewRates(time.Now(), map[string]map[string]float64{ + rates := NewRates(map[string]map[string]float64{ "USD": { "GBP": 0.77208, }, @@ -219,7 +182,7 @@ func TestGetRate_ReverseConversion(t *testing.T) { func TestGetRate_EmptyRates(t *testing.T) { // Setup: - rates := NewRates(time.Time{}, nil) + rates := NewRates(nil) // Execute: rate, err := rates.GetRate("USD", "EUR") @@ -232,7 +195,7 @@ func TestGetRate_EmptyRates(t *testing.T) { func TestGetRate_NotValidISOCurrency(t *testing.T) { // Setup: - rates := NewRates(time.Time{}, nil) + rates := NewRates(nil) testCases := []struct { from string diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index ec373094864..91910560476 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -2774,7 +2774,7 @@ func (e *mockBidExchange) getAuctionCurrencyRates(customRates *openrtb_ext.ExtRe 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) + return currency.NewRates(e.pbsRates) } usePbsRates := true @@ -2785,18 +2785,18 @@ func (e *mockBidExchange) getAuctionCurrencyRates(customRates *openrtb_ext.ExtRe 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) + return currency.NewRates(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 currency.NewRates(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)) + return currency.NewAggregateConversions(currency.NewRates(customRates.ConversionRates), currency.NewRates(e.pbsRates)) } // mockBidExchange is a well-behaved exchange that lists the bidders found in every bidRequest.Imp[i].Ext diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 213af9a0f05..a31a195c512 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -424,7 +424,6 @@ func TestMultiCurrencies(t *testing.T) { {currency: "USD", price: 1.3}, }, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "GBP": { "USD": 1.3050530256, @@ -449,7 +448,6 @@ func TestMultiCurrencies(t *testing.T) { {currency: "", price: 1.3}, }, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "GBP": { "USD": 1.3050530256, @@ -474,7 +472,6 @@ func TestMultiCurrencies(t *testing.T) { {currency: "EUR", price: 1.3}, }, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "GBP": { "USD": 1.3050530256, @@ -499,7 +496,6 @@ func TestMultiCurrencies(t *testing.T) { {currency: "GBP", price: 1.3}, }, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "GBP": { "USD": 1.3050530256, @@ -524,7 +520,6 @@ func TestMultiCurrencies(t *testing.T) { {currency: "GBP", price: 1.3}, }, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "GBP": { "USD": 1.3050530256, @@ -549,7 +544,6 @@ func TestMultiCurrencies(t *testing.T) { {currency: "GBP", price: 1.3}, }, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "GBP": { "USD": 1.3050530256, @@ -575,7 +569,6 @@ func TestMultiCurrencies(t *testing.T) { {currency: "DKK", price: 1.3}, }, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "GBP": { "USD": 1.3050530256, @@ -600,7 +593,6 @@ func TestMultiCurrencies(t *testing.T) { {currency: "CCC", price: 1.3}, }, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "GBP": { "USD": 1.3050530256, @@ -858,7 +850,6 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { expectedPickedCurrency: "EUR", expectedError: false, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "JPY": { "USD": 0.0089, @@ -879,7 +870,6 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { expectedPickedCurrency: "JPY", expectedError: false, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "JPY": { "USD": 0.0089, @@ -894,7 +884,6 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { expectedPickedCurrency: "USD", expectedError: false, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{ "JPY": { "USD": 0.0089, @@ -915,7 +904,6 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { expectedPickedCurrency: "", expectedError: true, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{}, }, description: "Case 4 - None allowed currencies in bid request are known, an error is returned", @@ -926,7 +914,6 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { expectedPickedCurrency: "USD", expectedError: false, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{}, }, description: "Case 5 - None allowed currencies in bid request are known but the default one (`USD`), no rates are set but default currency will be picked", @@ -937,7 +924,6 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { expectedPickedCurrency: "USD", expectedError: false, rates: currency.Rates{ - DataAsOf: time.Now(), Conversions: map[string]map[string]float64{}, }, description: "Case 6 - No allowed currencies specified in bid request, default one is picked: `USD`", diff --git a/exchange/exchange.go b/exchange/exchange.go index 1cedb5d9f43..87b28782e59 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -1015,7 +1015,7 @@ func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestC // 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) + return currency.NewRates(requestRates.ConversionRates) } // Both PBS and custom rates can be used, check if ConversionRates is not empty @@ -1026,7 +1026,7 @@ func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestC // 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()) + return currency.NewAggregateConversions(currency.NewRates(requestRates.ConversionRates), e.currencyConverter.Rates()) } func findCacheID(bid *pbsOrtbBid, auction *auction) (string, bool) { From 4a0e3659b7e7117acf7366b12a56a0c1b208a87a Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 19 Aug 2021 13:23:49 -0400 Subject: [PATCH 064/140] Remove old race conidtion tests (#1958) --- .../33acrosstest/params/race/banner.json | 6 --- .../33acrosstest/params/race/video.json | 6 --- .../acuityadstest/params/race/banner.json | 4 -- .../acuityadstest/params/race/native.json | 4 -- .../acuityadstest/params/race/video.json | 4 -- .../adagio/adagiotest/params/race/banner.json | 5 --- .../adagio/adagiotest/params/race/native.json | 5 --- .../adagio/adagiotest/params/race/video.json | 5 --- adapters/adf/adftest/params/race/native.json | 3 -- .../adform/adformtest/params/race/banner.json | 3 -- .../adform/adformtest/params/race/video.json | 3 -- .../adgenerationtest/params/race/banner.json | 3 -- .../adkerneltest/params/race/banner.json | 4 -- .../adkerneltest/params/race/video.json | 4 -- .../adkerneladntest/params/race/banner.json | 4 -- .../adkerneladntest/params/race/video.json | 4 -- adapters/adman/admantest/params/banner.json | 3 -- .../adman/admantest/params/race/banner.json | 3 -- .../adman/admantest/params/race/video.json | 3 -- adapters/adman/admantest/params/video.json | 3 -- .../admixertest/params/race/audio.json | 5 --- .../admixertest/params/race/banner.json | 5 --- .../admixertest/params/race/native.json | 5 --- .../admixertest/params/race/video.json | 5 --- .../adoceantest/params/race/banner.json | 5 --- .../adot/adottest/params/race/banner.json | 3 -- adapters/adot/adottest/params/race/video.json | 3 -- .../adpone/adponetest/params/race/banner.json | 1 - .../adprime/adprimetest/params/banner.json | 3 -- .../adprimetest/params/race/banner.json | 3 -- .../adprimetest/params/race/video.json | 3 -- .../adprime/adprimetest/params/video.json | 3 -- .../adtargettest/params/race/banner.json | 3 -- .../adtargettest/params/race/video.json | 3 -- .../adtelligenttest/params/race/banner.json | 3 -- .../adtelligenttest/params/race/video.json | 3 -- .../advangeliststest/params/race/banner.json | 4 -- .../advangeliststest/params/race/video.json | 4 -- .../adxcg/adxcgtest/params/race/banner.json | 1 - .../adyouliketest/params/race/banner.json | 3 -- .../adyouliketest/params/race/video.json | 3 -- adapters/aja/ajatest/params/race/banner.json | 3 -- adapters/aja/ajatest/params/race/video.json | 3 -- adapters/amx/amxtest/params/race/display.json | 1 - adapters/amx/amxtest/params/race/video.json | 1 - .../appnexustest/params/race/banner.json | 10 ----- .../appnexustest/params/race/video.json | 10 ----- .../params/race/banner.json | 3 -- .../axonix/axonixtest/params/race/banner.json | 3 -- .../axonix/axonixtest/params/race/video.json | 3 -- .../beachfronttest/params/race/banner.json | 4 -- .../beachfronttest/params/race/video.json | 4 -- .../beintootest/params/race/banner.json | 4 -- .../betweentest/params/race/banner.json | 4 -- .../bidmachinetest/params/race/banner.json | 5 --- .../bidmachinetest/params/race/video.json | 5 --- .../bidmyadztest/params/race/banner.json | 3 -- .../bidmyadztest/params/race/native.json | 3 -- .../bidmyadztest/params/race/video.json | 3 -- .../bidscubetest/params/race/banner.json | 3 -- .../bidscubetest/params/race/native.json | 3 -- .../bidscubetest/params/race/video.json | 3 -- .../params/race/banner.json | 3 -- .../params/race/video.json | 3 -- .../brightrolltest/params/race/banner.json | 3 -- .../brightrolltest/params/race/video.json | 3 -- .../colossus/colossustest/params/banner.json | 3 -- .../colossustest/params/race/banner.json | 3 -- .../colossustest/params/race/video.json | 3 -- .../colossus/colossustest/params/video.json | 3 -- .../connectadtest/params/race/banner.json | 5 --- .../conversanttest/params/race/banner.json | 3 -- .../conversanttest/params/race/video.json | 7 ---- .../datablockstest/params/race/banner.json | 4 -- .../datablockstest/params/race/native.json | 4 -- .../datablockstest/params/race/video.json | 4 -- .../decenteradstest/params/race/banner.json | 3 -- .../decenteradstest/params/race/video.json | 3 -- .../deepintenttest/params/banner.json | 3 -- .../deepintenttest/params/race/banner.json | 3 -- adapters/dmx/dmxtest/params/race/banner.json | 4 -- adapters/dmx/dmxtest/params/race/video.json | 4 -- .../evolutiontest/params/race/banner.json | 3 -- .../evolutiontest/params/race/native.json | 3 -- .../evolutiontest/params/race/video.json | 3 -- .../emx_digitaltest/params/race/banner.json | 4 -- .../emx_digitaltest/params/race/video.json | 4 -- .../engagebdrtest/params/race/banner.json | 3 -- .../engagebdrtest/params/race/native.json | 3 -- .../engagebdrtest/params/race/video.json | 3 -- .../eplanningtest/params/race/banner.json | 4 -- .../epom/epomtest/params/race/banner.json | 2 - .../epom/epomtest/params/race/native.json | 2 - adapters/epom/epomtest/params/race/video.json | 2 - .../gamma/gammatest/params/race/banner.json | 5 --- .../gamma/gammatest/params/race/video.json | 5 --- .../gamoshitest/params/race/banner.json | 3 -- .../gamoshitest/params/race/video.json | 3 -- .../grid/gridtest/params/race/banner.json | 14 ------- .../gumgum/gumgumtest/params/race/banner.json | 3 -- .../gumgum/gumgumtest/params/race/video.json | 3 -- .../params/race/banner.json | 12 ------ .../params/race/native.json | 12 ------ .../improvedigitaltest/params/race/video.json | 12 ------ .../inmobi/inmobitest/params/race/banner.json | 3 -- .../inmobi/inmobitest/params/race/video.json | 3 -- .../params/race/banner.json | 3 -- .../invibestest/params/race/banner.json | 3 -- adapters/ix/ixtest/params/race/audio.json | 4 -- adapters/ix/ixtest/params/race/banner.json | 4 -- adapters/ix/ixtest/params/race/native.json | 4 -- adapters/ix/ixtest/params/race/video.json | 4 -- .../jixie/jixietest/params/race/banner.json | 4 -- .../jixie/jixietest/params/race/video.json | 4 -- .../kayzen/kayzentest/params/race/banner.json | 5 --- .../kayzen/kayzentest/params/race/native.json | 5 --- .../kayzen/kayzentest/params/race/video.json | 5 --- .../krushmediatest/params/race/banner.json | 3 -- .../krushmediatest/params/race/native.json | 3 -- .../krushmediatest/params/race/video.json | 3 -- .../lockerdometest/params/race/banner.json | 3 -- .../logicadtest/params/race/banner.json | 3 -- .../lunamediatest/params/race/banner.json | 4 -- .../lunamediatest/params/race/video.json | 4 -- .../madvertisetest/params/race/banner.json | 4 -- .../madvertisetest/params/race/video.json | 3 -- .../marsmediatest/params/race/banner.json | 3 -- .../marsmediatest/params/race/video.json | 3 -- .../params/race/banner-direct-route.json | 3 -- .../params/race/banner-rtb-route.json | 3 -- .../params/race/video-direct-route.json | 3 -- .../params/race/video-rtb-route.json | 3 -- .../mobilefusetest/params/race/banner.json | 5 --- .../mobilefusetest/params/race/video.json | 5 --- .../params/race/banner.json | 3 -- .../ninthdecimaltest/params/race/banner.json | 4 -- .../ninthdecimaltest/params/race/video.json | 4 -- .../onetag/onetagtest/params/race/banner.json | 4 -- .../onetag/onetagtest/params/race/native.json | 4 -- .../onetag/onetagtest/params/race/video.json | 4 -- .../openx/openxtest/params/race/banner.json | 6 --- .../openx/openxtest/params/race/video.json | 6 --- .../orbiddertest/params/race/banner.json | 5 --- .../outbraintest/params/race/banner.json | 10 ----- .../outbraintest/params/race/native.json | 10 ----- .../pangle/pangletest/params/race/banner.json | 5 --- .../pangle/pangletest/params/race/native.json | 5 --- .../pangle/pangletest/params/race/video.json | 5 --- .../pubmatictest/params/race/banner.json | 14 ------- .../pubmatictest/params/race/video.json | 14 ------- .../pulsepointtest/params/race/banner.json | 4 -- .../pulsepointtest/params/race/native.json | 4 -- .../pulsepointtest/params/race/video.json | 4 -- .../rtbhousetest/params/race/banner.json | 1 - .../rubicontest/params/race/banner.json | 5 --- .../rubicontest/params/race/video.json | 5 --- .../salunamediatest/params/race/banner.json | 3 -- .../salunamediatest/params/race/native.json | 3 -- .../salunamediatest/params/race/video.json | 3 -- .../sharethroughtest/params/race/banner.json | 5 --- .../sharethroughtest/params/race/native.json | 5 --- .../silvermobtest/params/race/banner.json | 4 -- .../silvermobtest/params/race/native.json | 4 -- .../silvermobtest/params/race/video.json | 4 -- .../smaato/smaatotest/params/race/banner.json | 4 -- .../smaato/smaatotest/params/race/video.json | 4 -- .../smartadservertest/params/race/banner.json | 7 ---- .../smartadservertest/params/race/video.json | 7 ---- .../smarthubtest/params/race/banner.json | 5 --- .../smarthubtest/params/race/native.json | 5 --- .../smarthubtest/params/race/video.json | 5 --- .../smartrtbtest/params/race/banner.json | 5 --- .../smartrtbtest/params/race/video.json | 5 --- .../smartyadstest/params/race/banner.json | 5 --- .../smartyadstest/params/race/native.json | 5 --- .../smartyadstest/params/race/video.json | 5 --- .../smilewantedtest/params/race/banner.json | 3 -- .../smilewantedtest/params/race/video.json | 3 -- .../somoaudiencetest/params/race/banner.json | 3 -- .../somoaudiencetest/params/race/video.json | 3 -- .../sonobi/sonobitest/params/race/banner.json | 3 -- .../sonobi/sonobitest/params/race/video.json | 3 -- adapters/sovrn/sovrntest/params/banner.json | 3 -- .../synacormediatest/params/banner.json | 4 -- .../synacormediatest/params/video.json | 4 -- .../tappx/tappxtest/params/race/banner.json | 5 --- .../tappx/tappxtest/params/race/video.json | 5 --- .../telariatest/params/race/video.json | 4 -- .../ucfunneltest/params/race/banner.json | 5 --- .../ucfunneltest/params/race/video.json | 5 --- .../unicorntest/params/race/banner.json | 6 --- .../unruly/unrulytest/params/race/video.json | 4 -- .../verizonmediatest/params/race/banner.json | 4 -- .../visx/visxtest/params/race/banner.json | 7 ---- .../yeahmobitest/params/race/banner.json | 5 --- .../yieldonetest/params/race/banner.json | 4 -- .../params/race/banner.json | 4 -- .../params/race/native.json | 4 -- .../zeroclickfraudtest/params/race/video.json | 4 -- exchange/exchange_test.go | 37 +++---------------- exchange/utils_test.go | 2 +- 201 files changed, 7 insertions(+), 865 deletions(-) delete mode 100644 adapters/33across/33acrosstest/params/race/banner.json delete mode 100644 adapters/33across/33acrosstest/params/race/video.json delete mode 100644 adapters/acuityads/acuityadstest/params/race/banner.json delete mode 100644 adapters/acuityads/acuityadstest/params/race/native.json delete mode 100644 adapters/acuityads/acuityadstest/params/race/video.json delete mode 100644 adapters/adagio/adagiotest/params/race/banner.json delete mode 100644 adapters/adagio/adagiotest/params/race/native.json delete mode 100644 adapters/adagio/adagiotest/params/race/video.json delete mode 100644 adapters/adf/adftest/params/race/native.json delete mode 100644 adapters/adform/adformtest/params/race/banner.json delete mode 100644 adapters/adform/adformtest/params/race/video.json delete mode 100644 adapters/adgeneration/adgenerationtest/params/race/banner.json delete mode 100644 adapters/adkernel/adkerneltest/params/race/banner.json delete mode 100644 adapters/adkernel/adkerneltest/params/race/video.json delete mode 100644 adapters/adkernelAdn/adkerneladntest/params/race/banner.json delete mode 100644 adapters/adkernelAdn/adkerneladntest/params/race/video.json delete mode 100644 adapters/adman/admantest/params/banner.json delete mode 100644 adapters/adman/admantest/params/race/banner.json delete mode 100644 adapters/adman/admantest/params/race/video.json delete mode 100644 adapters/adman/admantest/params/video.json delete mode 100644 adapters/admixer/admixertest/params/race/audio.json delete mode 100644 adapters/admixer/admixertest/params/race/banner.json delete mode 100644 adapters/admixer/admixertest/params/race/native.json delete mode 100644 adapters/admixer/admixertest/params/race/video.json delete mode 100644 adapters/adocean/adoceantest/params/race/banner.json delete mode 100644 adapters/adot/adottest/params/race/banner.json delete mode 100644 adapters/adot/adottest/params/race/video.json delete mode 100644 adapters/adpone/adponetest/params/race/banner.json delete mode 100644 adapters/adprime/adprimetest/params/banner.json delete mode 100644 adapters/adprime/adprimetest/params/race/banner.json delete mode 100644 adapters/adprime/adprimetest/params/race/video.json delete mode 100644 adapters/adprime/adprimetest/params/video.json delete mode 100644 adapters/adtarget/adtargettest/params/race/banner.json delete mode 100644 adapters/adtarget/adtargettest/params/race/video.json delete mode 100644 adapters/adtelligent/adtelligenttest/params/race/banner.json delete mode 100644 adapters/adtelligent/adtelligenttest/params/race/video.json delete mode 100644 adapters/advangelists/advangeliststest/params/race/banner.json delete mode 100644 adapters/advangelists/advangeliststest/params/race/video.json delete mode 100644 adapters/adxcg/adxcgtest/params/race/banner.json delete mode 100644 adapters/adyoulike/adyouliketest/params/race/banner.json delete mode 100644 adapters/adyoulike/adyouliketest/params/race/video.json delete mode 100644 adapters/aja/ajatest/params/race/banner.json delete mode 100644 adapters/aja/ajatest/params/race/video.json delete mode 100644 adapters/amx/amxtest/params/race/display.json delete mode 100644 adapters/amx/amxtest/params/race/video.json delete mode 100644 adapters/appnexus/appnexustest/params/race/banner.json delete mode 100644 adapters/appnexus/appnexustest/params/race/video.json delete mode 100644 adapters/audienceNetwork/audienceNetworktest/params/race/banner.json delete mode 100644 adapters/axonix/axonixtest/params/race/banner.json delete mode 100644 adapters/axonix/axonixtest/params/race/video.json delete mode 100644 adapters/beachfront/beachfronttest/params/race/banner.json delete mode 100644 adapters/beachfront/beachfronttest/params/race/video.json delete mode 100644 adapters/beintoo/beintootest/params/race/banner.json delete mode 100644 adapters/between/betweentest/params/race/banner.json delete mode 100644 adapters/bidmachine/bidmachinetest/params/race/banner.json delete mode 100644 adapters/bidmachine/bidmachinetest/params/race/video.json delete mode 100644 adapters/bidmyadz/bidmyadztest/params/race/banner.json delete mode 100644 adapters/bidmyadz/bidmyadztest/params/race/native.json delete mode 100644 adapters/bidmyadz/bidmyadztest/params/race/video.json delete mode 100644 adapters/bidscube/bidscubetest/params/race/banner.json delete mode 100644 adapters/bidscube/bidscubetest/params/race/native.json delete mode 100644 adapters/bidscube/bidscubetest/params/race/video.json delete mode 100644 adapters/bmtm/brightmountainmediatest/params/race/banner.json delete mode 100644 adapters/bmtm/brightmountainmediatest/params/race/video.json delete mode 100644 adapters/brightroll/brightrolltest/params/race/banner.json delete mode 100644 adapters/brightroll/brightrolltest/params/race/video.json delete mode 100644 adapters/colossus/colossustest/params/banner.json delete mode 100644 adapters/colossus/colossustest/params/race/banner.json delete mode 100644 adapters/colossus/colossustest/params/race/video.json delete mode 100644 adapters/colossus/colossustest/params/video.json delete mode 100644 adapters/connectad/connectadtest/params/race/banner.json delete mode 100644 adapters/conversant/conversanttest/params/race/banner.json delete mode 100644 adapters/conversant/conversanttest/params/race/video.json delete mode 100644 adapters/datablocks/datablockstest/params/race/banner.json delete mode 100644 adapters/datablocks/datablockstest/params/race/native.json delete mode 100644 adapters/datablocks/datablockstest/params/race/video.json delete mode 100644 adapters/decenterads/decenteradstest/params/race/banner.json delete mode 100644 adapters/decenterads/decenteradstest/params/race/video.json delete mode 100644 adapters/deepintent/deepintenttest/params/banner.json delete mode 100644 adapters/deepintent/deepintenttest/params/race/banner.json delete mode 100644 adapters/dmx/dmxtest/params/race/banner.json delete mode 100644 adapters/dmx/dmxtest/params/race/video.json delete mode 100644 adapters/e_volution/evolutiontest/params/race/banner.json delete mode 100644 adapters/e_volution/evolutiontest/params/race/native.json delete mode 100644 adapters/e_volution/evolutiontest/params/race/video.json delete mode 100644 adapters/emx_digital/emx_digitaltest/params/race/banner.json delete mode 100644 adapters/emx_digital/emx_digitaltest/params/race/video.json delete mode 100644 adapters/engagebdr/engagebdrtest/params/race/banner.json delete mode 100644 adapters/engagebdr/engagebdrtest/params/race/native.json delete mode 100644 adapters/engagebdr/engagebdrtest/params/race/video.json delete mode 100644 adapters/eplanning/eplanningtest/params/race/banner.json delete mode 100644 adapters/epom/epomtest/params/race/banner.json delete mode 100644 adapters/epom/epomtest/params/race/native.json delete mode 100644 adapters/epom/epomtest/params/race/video.json delete mode 100644 adapters/gamma/gammatest/params/race/banner.json delete mode 100644 adapters/gamma/gammatest/params/race/video.json delete mode 100644 adapters/gamoshi/gamoshitest/params/race/banner.json delete mode 100644 adapters/gamoshi/gamoshitest/params/race/video.json delete mode 100644 adapters/grid/gridtest/params/race/banner.json delete mode 100644 adapters/gumgum/gumgumtest/params/race/banner.json delete mode 100644 adapters/gumgum/gumgumtest/params/race/video.json delete mode 100644 adapters/improvedigital/improvedigitaltest/params/race/banner.json delete mode 100644 adapters/improvedigital/improvedigitaltest/params/race/native.json delete mode 100644 adapters/improvedigital/improvedigitaltest/params/race/video.json delete mode 100644 adapters/inmobi/inmobitest/params/race/banner.json delete mode 100644 adapters/inmobi/inmobitest/params/race/video.json delete mode 100644 adapters/interactiveoffers/interactiveofferstest/params/race/banner.json delete mode 100644 adapters/invibes/invibestest/params/race/banner.json delete mode 100644 adapters/ix/ixtest/params/race/audio.json delete mode 100644 adapters/ix/ixtest/params/race/banner.json delete mode 100644 adapters/ix/ixtest/params/race/native.json delete mode 100644 adapters/ix/ixtest/params/race/video.json delete mode 100644 adapters/jixie/jixietest/params/race/banner.json delete mode 100644 adapters/jixie/jixietest/params/race/video.json delete mode 100644 adapters/kayzen/kayzentest/params/race/banner.json delete mode 100644 adapters/kayzen/kayzentest/params/race/native.json delete mode 100644 adapters/kayzen/kayzentest/params/race/video.json delete mode 100644 adapters/krushmedia/krushmediatest/params/race/banner.json delete mode 100644 adapters/krushmedia/krushmediatest/params/race/native.json delete mode 100644 adapters/krushmedia/krushmediatest/params/race/video.json delete mode 100644 adapters/lockerdome/lockerdometest/params/race/banner.json delete mode 100644 adapters/logicad/logicadtest/params/race/banner.json delete mode 100644 adapters/lunamedia/lunamediatest/params/race/banner.json delete mode 100644 adapters/lunamedia/lunamediatest/params/race/video.json delete mode 100644 adapters/madvertise/madvertisetest/params/race/banner.json delete mode 100644 adapters/madvertise/madvertisetest/params/race/video.json delete mode 100644 adapters/marsmedia/marsmediatest/params/race/banner.json delete mode 100644 adapters/marsmedia/marsmediatest/params/race/video.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json delete mode 100644 adapters/mobilefuse/mobilefusetest/params/race/banner.json delete mode 100644 adapters/mobilefuse/mobilefusetest/params/race/video.json delete mode 100644 adapters/nanointeractive/nanointeractivetest/params/race/banner.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/params/race/banner.json delete mode 100644 adapters/ninthdecimal/ninthdecimaltest/params/race/video.json delete mode 100644 adapters/onetag/onetagtest/params/race/banner.json delete mode 100644 adapters/onetag/onetagtest/params/race/native.json delete mode 100644 adapters/onetag/onetagtest/params/race/video.json delete mode 100644 adapters/openx/openxtest/params/race/banner.json delete mode 100644 adapters/openx/openxtest/params/race/video.json delete mode 100644 adapters/orbidder/orbiddertest/params/race/banner.json delete mode 100644 adapters/outbrain/outbraintest/params/race/banner.json delete mode 100644 adapters/outbrain/outbraintest/params/race/native.json delete mode 100644 adapters/pangle/pangletest/params/race/banner.json delete mode 100644 adapters/pangle/pangletest/params/race/native.json delete mode 100644 adapters/pangle/pangletest/params/race/video.json delete mode 100644 adapters/pubmatic/pubmatictest/params/race/banner.json delete mode 100644 adapters/pubmatic/pubmatictest/params/race/video.json delete mode 100644 adapters/pulsepoint/pulsepointtest/params/race/banner.json delete mode 100644 adapters/pulsepoint/pulsepointtest/params/race/native.json delete mode 100644 adapters/pulsepoint/pulsepointtest/params/race/video.json delete mode 100644 adapters/rtbhouse/rtbhousetest/params/race/banner.json delete mode 100644 adapters/rubicon/rubicontest/params/race/banner.json delete mode 100644 adapters/rubicon/rubicontest/params/race/video.json delete mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/banner.json delete mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/native.json delete mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/video.json delete mode 100644 adapters/sharethrough/sharethroughtest/params/race/banner.json delete mode 100644 adapters/sharethrough/sharethroughtest/params/race/native.json delete mode 100644 adapters/silvermob/silvermobtest/params/race/banner.json delete mode 100644 adapters/silvermob/silvermobtest/params/race/native.json delete mode 100644 adapters/silvermob/silvermobtest/params/race/video.json delete mode 100644 adapters/smaato/smaatotest/params/race/banner.json delete mode 100644 adapters/smaato/smaatotest/params/race/video.json delete mode 100644 adapters/smartadserver/smartadservertest/params/race/banner.json delete mode 100644 adapters/smartadserver/smartadservertest/params/race/video.json delete mode 100644 adapters/smarthub/smarthubtest/params/race/banner.json delete mode 100644 adapters/smarthub/smarthubtest/params/race/native.json delete mode 100644 adapters/smarthub/smarthubtest/params/race/video.json delete mode 100644 adapters/smartrtb/smartrtbtest/params/race/banner.json delete mode 100644 adapters/smartrtb/smartrtbtest/params/race/video.json delete mode 100644 adapters/smartyads/smartyadstest/params/race/banner.json delete mode 100644 adapters/smartyads/smartyadstest/params/race/native.json delete mode 100644 adapters/smartyads/smartyadstest/params/race/video.json delete mode 100644 adapters/smilewanted/smilewantedtest/params/race/banner.json delete mode 100644 adapters/smilewanted/smilewantedtest/params/race/video.json delete mode 100644 adapters/somoaudience/somoaudiencetest/params/race/banner.json delete mode 100644 adapters/somoaudience/somoaudiencetest/params/race/video.json delete mode 100644 adapters/sonobi/sonobitest/params/race/banner.json delete mode 100644 adapters/sonobi/sonobitest/params/race/video.json delete mode 100644 adapters/sovrn/sovrntest/params/banner.json delete mode 100644 adapters/synacormedia/synacormediatest/params/banner.json delete mode 100644 adapters/synacormedia/synacormediatest/params/video.json delete mode 100644 adapters/tappx/tappxtest/params/race/banner.json delete mode 100644 adapters/tappx/tappxtest/params/race/video.json delete mode 100644 adapters/telaria/telariatest/params/race/video.json delete mode 100644 adapters/ucfunnel/ucfunneltest/params/race/banner.json delete mode 100644 adapters/ucfunnel/ucfunneltest/params/race/video.json delete mode 100644 adapters/unicorn/unicorntest/params/race/banner.json delete mode 100644 adapters/unruly/unrulytest/params/race/video.json delete mode 100644 adapters/verizonmedia/verizonmediatest/params/race/banner.json delete mode 100644 adapters/visx/visxtest/params/race/banner.json delete mode 100644 adapters/yeahmobi/yeahmobitest/params/race/banner.json delete mode 100644 adapters/yieldone/yieldonetest/params/race/banner.json delete mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json delete mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json delete mode 100644 adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json diff --git a/adapters/33across/33acrosstest/params/race/banner.json b/adapters/33across/33acrosstest/params/race/banner.json deleted file mode 100644 index 9df849ad94b..00000000000 --- a/adapters/33across/33acrosstest/params/race/banner.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "productId": "siab", - "siteId": "33across", - "zoneId": "33AcrossZone" - } - \ No newline at end of file diff --git a/adapters/33across/33acrosstest/params/race/video.json b/adapters/33across/33acrosstest/params/race/video.json deleted file mode 100644 index 9df849ad94b..00000000000 --- a/adapters/33across/33acrosstest/params/race/video.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "productId": "siab", - "siteId": "33across", - "zoneId": "33AcrossZone" - } - \ No newline at end of file diff --git a/adapters/acuityads/acuityadstest/params/race/banner.json b/adapters/acuityads/acuityadstest/params/race/banner.json deleted file mode 100644 index 89f008afe0f..00000000000 --- a/adapters/acuityads/acuityadstest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "host": "ep1", - "accountid": "hash" -} diff --git a/adapters/acuityads/acuityadstest/params/race/native.json b/adapters/acuityads/acuityadstest/params/race/native.json deleted file mode 100644 index a7354b3b42a..00000000000 --- a/adapters/acuityads/acuityadstest/params/race/native.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "host": "ep1", - "accountid": "hash" -} \ No newline at end of file diff --git a/adapters/acuityads/acuityadstest/params/race/video.json b/adapters/acuityads/acuityadstest/params/race/video.json deleted file mode 100644 index a7354b3b42a..00000000000 --- a/adapters/acuityads/acuityadstest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "host": "ep1", - "accountid": "hash" -} \ No newline at end of file diff --git a/adapters/adagio/adagiotest/params/race/banner.json b/adapters/adagio/adagiotest/params/race/banner.json deleted file mode 100644 index 66694466d84..00000000000 --- a/adapters/adagio/adagiotest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 4affd5b232c..00000000000 --- a/adapters/adagio/adagiotest/params/race/native.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 4affd5b232c..00000000000 --- a/adapters/adagio/adagiotest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "organizationId": "1000", - "site": "site-name", - "placement": "in_article" -} diff --git a/adapters/adf/adftest/params/race/native.json b/adapters/adf/adftest/params/race/native.json deleted file mode 100644 index 79535d85da4..00000000000 --- a/adapters/adf/adftest/params/race/native.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mid": "828782" -} diff --git a/adapters/adform/adformtest/params/race/banner.json b/adapters/adform/adformtest/params/race/banner.json deleted file mode 100644 index 21a9140d7b9..00000000000 --- a/adapters/adform/adformtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mid": "292063" -} diff --git a/adapters/adform/adformtest/params/race/video.json b/adapters/adform/adformtest/params/race/video.json deleted file mode 100644 index 51f8f1b94d2..00000000000 --- a/adapters/adform/adformtest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mid": "858300" -} diff --git a/adapters/adgeneration/adgenerationtest/params/race/banner.json b/adapters/adgeneration/adgenerationtest/params/race/banner.json deleted file mode 100644 index 0aa81b38851..00000000000 --- a/adapters/adgeneration/adgenerationtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "id": "58278" -} diff --git a/adapters/adkernel/adkerneltest/params/race/banner.json b/adapters/adkernel/adkerneltest/params/race/banner.json deleted file mode 100644 index 7b9fcd1a959..00000000000 --- a/adapters/adkernel/adkerneltest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "zoneId": 1, - "host": "cpm.adkernel.com" -} \ No newline at end of file diff --git a/adapters/adkernel/adkerneltest/params/race/video.json b/adapters/adkernel/adkerneltest/params/race/video.json deleted file mode 100644 index 7b9fcd1a959..00000000000 --- a/adapters/adkernel/adkerneltest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "zoneId": 1, - "host": "cpm.adkernel.com" -} \ No newline at end of file diff --git a/adapters/adkernelAdn/adkerneladntest/params/race/banner.json b/adapters/adkernelAdn/adkerneladntest/params/race/banner.json deleted file mode 100644 index 6910fb9297d..00000000000 --- a/adapters/adkernelAdn/adkerneladntest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubId": 1, - "host": "tag.adkernel.com" -} \ No newline at end of file diff --git a/adapters/adkernelAdn/adkerneladntest/params/race/video.json b/adapters/adkernelAdn/adkerneladntest/params/race/video.json deleted file mode 100644 index 6910fb9297d..00000000000 --- a/adapters/adkernelAdn/adkerneladntest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubId": 1, - "host": "tag.adkernel.com" -} \ No newline at end of file diff --git a/adapters/adman/admantest/params/banner.json b/adapters/adman/admantest/params/banner.json deleted file mode 100644 index 03fa8f3f2d8..00000000000 --- a/adapters/adman/admantest/params/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "16" -} \ No newline at end of file diff --git a/adapters/adman/admantest/params/race/banner.json b/adapters/adman/admantest/params/race/banner.json deleted file mode 100644 index 03fa8f3f2d8..00000000000 --- a/adapters/adman/admantest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "16" -} \ No newline at end of file diff --git a/adapters/adman/admantest/params/race/video.json b/adapters/adman/admantest/params/race/video.json deleted file mode 100644 index e776c928a7e..00000000000 --- a/adapters/adman/admantest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "22" -} \ No newline at end of file diff --git a/adapters/adman/admantest/params/video.json b/adapters/adman/admantest/params/video.json deleted file mode 100644 index e776c928a7e..00000000000 --- a/adapters/adman/admantest/params/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "22" -} \ No newline at end of file diff --git a/adapters/admixer/admixertest/params/race/audio.json b/adapters/admixer/admixertest/params/race/audio.json deleted file mode 100644 index f9aa771e4b1..00000000000 --- a/adapters/admixer/admixertest/params/race/audio.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "zone": "473e443c-43d0-423d-a8d7-a302637a01d8", - "customFloor": 0.1, - "customParams": {"foo": "bar"} -} \ No newline at end of file diff --git a/adapters/admixer/admixertest/params/race/banner.json b/adapters/admixer/admixertest/params/race/banner.json deleted file mode 100644 index 03510f7e1ca..00000000000 --- a/adapters/admixer/admixertest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", - "customFloor": 0.1, - "customParams": {"foo": "bar"} -} diff --git a/adapters/admixer/admixertest/params/race/native.json b/adapters/admixer/admixertest/params/race/native.json deleted file mode 100644 index 65712a30228..00000000000 --- a/adapters/admixer/admixertest/params/race/native.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "zone": "b1fbebfc-7155-4922-bb86-615e7f3d6eef", - "customFloor": 0.1, - "customParams": {"foo": "bar"} -} \ No newline at end of file diff --git a/adapters/admixer/admixertest/params/race/video.json b/adapters/admixer/admixertest/params/race/video.json deleted file mode 100644 index 5e9bc6e59fd..00000000000 --- a/adapters/admixer/admixertest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "zone": "ac7fa772-d7be-48cc-820b-e21728e434fe", - "customFloor": 0.1, - "customParams": {"foo": "bar"} -} diff --git a/adapters/adocean/adoceantest/params/race/banner.json b/adapters/adocean/adoceantest/params/race/banner.json deleted file mode 100644 index f9f38481350..00000000000 --- a/adapters/adocean/adoceantest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "emiter": "myao.adocean.pl", - "masterId": "tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7", - "slaveId": "adoceanmyaozpniqismex" -} diff --git a/adapters/adot/adottest/params/race/banner.json b/adapters/adot/adottest/params/race/banner.json deleted file mode 100644 index ada77aa4440..00000000000 --- a/adapters/adot/adottest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placementId": "ee234aac-113" -} \ No newline at end of file diff --git a/adapters/adot/adottest/params/race/video.json b/adapters/adot/adottest/params/race/video.json deleted file mode 100644 index 37808cd2ddc..00000000000 --- a/adapters/adot/adottest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placementId": "ee234aac-112" -} \ No newline at end of file diff --git a/adapters/adpone/adponetest/params/race/banner.json b/adapters/adpone/adponetest/params/race/banner.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/adapters/adpone/adponetest/params/race/banner.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/adapters/adprime/adprimetest/params/banner.json b/adapters/adprime/adprimetest/params/banner.json deleted file mode 100644 index e3f4cb7605a..00000000000 --- a/adapters/adprime/adprimetest/params/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "1" -} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/race/banner.json b/adapters/adprime/adprimetest/params/race/banner.json deleted file mode 100644 index e3f4cb7605a..00000000000 --- a/adapters/adprime/adprimetest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "1" -} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/race/video.json b/adapters/adprime/adprimetest/params/race/video.json deleted file mode 100644 index c8d14757903..00000000000 --- a/adapters/adprime/adprimetest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "288" -} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/video.json b/adapters/adprime/adprimetest/params/video.json deleted file mode 100644 index c8d14757903..00000000000 --- a/adapters/adprime/adprimetest/params/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "288" -} \ No newline at end of file diff --git a/adapters/adtarget/adtargettest/params/race/banner.json b/adapters/adtarget/adtargettest/params/race/banner.json deleted file mode 100644 index 1d6658c71ab..00000000000 --- a/adapters/adtarget/adtargettest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "aid": 350975 -} diff --git a/adapters/adtarget/adtargettest/params/race/video.json b/adapters/adtarget/adtargettest/params/race/video.json deleted file mode 100644 index fe4207ef05c..00000000000 --- a/adapters/adtarget/adtargettest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "aid": 331133 -} diff --git a/adapters/adtelligent/adtelligenttest/params/race/banner.json b/adapters/adtelligent/adtelligenttest/params/race/banner.json deleted file mode 100644 index 1d6658c71ab..00000000000 --- a/adapters/adtelligent/adtelligenttest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "aid": 350975 -} diff --git a/adapters/adtelligent/adtelligenttest/params/race/video.json b/adapters/adtelligent/adtelligenttest/params/race/video.json deleted file mode 100644 index fe4207ef05c..00000000000 --- a/adapters/adtelligent/adtelligenttest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "aid": 331133 -} diff --git a/adapters/advangelists/advangeliststest/params/race/banner.json b/adapters/advangelists/advangeliststest/params/race/banner.json deleted file mode 100644 index 2eed8f2ec4e..00000000000 --- a/adapters/advangelists/advangeliststest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" -} \ No newline at end of file diff --git a/adapters/advangelists/advangeliststest/params/race/video.json b/adapters/advangelists/advangeliststest/params/race/video.json deleted file mode 100644 index 2eed8f2ec4e..00000000000 --- a/adapters/advangelists/advangeliststest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" -} \ No newline at end of file diff --git a/adapters/adxcg/adxcgtest/params/race/banner.json b/adapters/adxcg/adxcgtest/params/race/banner.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/adapters/adxcg/adxcgtest/params/race/banner.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/adapters/adyoulike/adyouliketest/params/race/banner.json b/adapters/adyoulike/adyouliketest/params/race/banner.json deleted file mode 100644 index 726ca878c05..00000000000 --- a/adapters/adyoulike/adyouliketest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placement": "19f1b372c7548ec1fe734d2c9f8dc688" -} diff --git a/adapters/adyoulike/adyouliketest/params/race/video.json b/adapters/adyoulike/adyouliketest/params/race/video.json deleted file mode 100644 index d0883f5e04a..00000000000 --- a/adapters/adyoulike/adyouliketest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placement": "19f1b372c7548ec1fe734d2c9f8dc688" -} diff --git a/adapters/aja/ajatest/params/race/banner.json b/adapters/aja/ajatest/params/race/banner.json deleted file mode 100644 index 6d50c2d1880..00000000000 --- a/adapters/aja/ajatest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "asi": "abc123" -} \ No newline at end of file diff --git a/adapters/aja/ajatest/params/race/video.json b/adapters/aja/ajatest/params/race/video.json deleted file mode 100644 index 6d50c2d1880..00000000000 --- a/adapters/aja/ajatest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "asi": "abc123" -} \ No newline at end of file diff --git a/adapters/amx/amxtest/params/race/display.json b/adapters/amx/amxtest/params/race/display.json deleted file mode 100644 index bd101e95a25..00000000000 --- a/adapters/amx/amxtest/params/race/display.json +++ /dev/null @@ -1 +0,0 @@ -{"tagId":"sample345", "adUnitId": "sampleAdUnitID"} \ No newline at end of file diff --git a/adapters/amx/amxtest/params/race/video.json b/adapters/amx/amxtest/params/race/video.json deleted file mode 100644 index d2f11bf80b4..00000000000 --- a/adapters/amx/amxtest/params/race/video.json +++ /dev/null @@ -1 +0,0 @@ -{"tagId": "sample123", "adUnitId": "sampleAdUnitID"} \ No newline at end of file diff --git a/adapters/appnexus/appnexustest/params/race/banner.json b/adapters/appnexus/appnexustest/params/race/banner.json deleted file mode 100644 index a37e0036357..00000000000 --- a/adapters/appnexus/appnexustest/params/race/banner.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "placement_id": 1, - "reserve": 20, - "position": "below", - "traffic_source_code": "trafficSource", - "keywords": [ - {"key": "foo", "value": ["bar","baz"]}, - {"key": "valueless"} - ] -} diff --git a/adapters/appnexus/appnexustest/params/race/video.json b/adapters/appnexus/appnexustest/params/race/video.json deleted file mode 100644 index a37e0036357..00000000000 --- a/adapters/appnexus/appnexustest/params/race/video.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "placement_id": 1, - "reserve": 20, - "position": "below", - "traffic_source_code": "trafficSource", - "keywords": [ - {"key": "foo", "value": ["bar","baz"]}, - {"key": "valueless"} - ] -} diff --git a/adapters/audienceNetwork/audienceNetworktest/params/race/banner.json b/adapters/audienceNetwork/audienceNetworktest/params/race/banner.json deleted file mode 100644 index 6b8bd390da7..00000000000 --- a/adapters/audienceNetwork/audienceNetworktest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placementId": "555555555555555_555555555555555" -} diff --git a/adapters/axonix/axonixtest/params/race/banner.json b/adapters/axonix/axonixtest/params/race/banner.json deleted file mode 100644 index 136be9b1517..00000000000 --- a/adapters/axonix/axonixtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "supplyId": "supply-test" -} diff --git a/adapters/axonix/axonixtest/params/race/video.json b/adapters/axonix/axonixtest/params/race/video.json deleted file mode 100644 index 136be9b1517..00000000000 --- a/adapters/axonix/axonixtest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "supplyId": "supply-test" -} diff --git a/adapters/beachfront/beachfronttest/params/race/banner.json b/adapters/beachfront/beachfronttest/params/race/banner.json deleted file mode 100644 index 71db3b9c616..00000000000 --- a/adapters/beachfront/beachfronttest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "bidfloor": 0.02, - "appId": "3b16770b-17af-4d22-daff-9606bdf2c9c3" -} diff --git a/adapters/beachfront/beachfronttest/params/race/video.json b/adapters/beachfront/beachfronttest/params/race/video.json deleted file mode 100644 index 6c1bd337f09..00000000000 --- a/adapters/beachfront/beachfronttest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "bidfloor":0.01, - "appId":"11bc5dd5-7421-4dd8-c926-40fa653bec76" -} diff --git a/adapters/beintoo/beintootest/params/race/banner.json b/adapters/beintoo/beintootest/params/race/banner.json deleted file mode 100644 index 5155c92d11e..00000000000 --- a/adapters/beintoo/beintootest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "tagid": "25251", - "bidfloor": "0.01" -} diff --git a/adapters/between/betweentest/params/race/banner.json b/adapters/between/betweentest/params/race/banner.json deleted file mode 100644 index 7d64a880a17..00000000000 --- a/adapters/between/betweentest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "host": "127.0.0.1", - "publisher_id": 1 -} diff --git a/adapters/bidmachine/bidmachinetest/params/race/banner.json b/adapters/bidmachine/bidmachinetest/params/race/banner.json deleted file mode 100644 index 0402e4a129b..00000000000 --- a/adapters/bidmachine/bidmachinetest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "seller_id": "1", - "path": "auction/rtb/v2", - "host": "api-eu" -} \ No newline at end of file diff --git a/adapters/bidmachine/bidmachinetest/params/race/video.json b/adapters/bidmachine/bidmachinetest/params/race/video.json deleted file mode 100644 index 0402e4a129b..00000000000 --- a/adapters/bidmachine/bidmachinetest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "seller_id": "1", - "path": "auction/rtb/v2", - "host": "api-eu" -} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/banner.json b/adapters/bidmyadz/bidmyadztest/params/race/banner.json deleted file mode 100644 index 18dce42f2c4..00000000000 --- a/adapters/bidmyadz/bidmyadztest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placementId": "tbanner" -} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/native.json b/adapters/bidmyadz/bidmyadztest/params/race/native.json deleted file mode 100644 index 0600af3a894..00000000000 --- a/adapters/bidmyadz/bidmyadztest/params/race/native.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placementId": "tnative" -} \ No newline at end of file diff --git a/adapters/bidmyadz/bidmyadztest/params/race/video.json b/adapters/bidmyadz/bidmyadztest/params/race/video.json deleted file mode 100644 index 85478bf22c5..00000000000 --- a/adapters/bidmyadz/bidmyadztest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placementId": "tvideo" -} \ No newline at end of file diff --git a/adapters/bidscube/bidscubetest/params/race/banner.json b/adapters/bidscube/bidscubetest/params/race/banner.json deleted file mode 100644 index dbdac1ad995..00000000000 --- a/adapters/bidscube/bidscubetest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "6" -} \ No newline at end of file diff --git a/adapters/bidscube/bidscubetest/params/race/native.json b/adapters/bidscube/bidscubetest/params/race/native.json deleted file mode 100644 index 715fa3b1723..00000000000 --- a/adapters/bidscube/bidscubetest/params/race/native.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "8" -} diff --git a/adapters/bidscube/bidscubetest/params/race/video.json b/adapters/bidscube/bidscubetest/params/race/video.json deleted file mode 100644 index 6e2e0b3803b..00000000000 --- a/adapters/bidscube/bidscubetest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "7" -} \ No newline at end of file diff --git a/adapters/bmtm/brightmountainmediatest/params/race/banner.json b/adapters/bmtm/brightmountainmediatest/params/race/banner.json deleted file mode 100644 index efafd65f027..00000000000 --- a/adapters/bmtm/brightmountainmediatest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placement_id": 329 -} \ No newline at end of file diff --git a/adapters/bmtm/brightmountainmediatest/params/race/video.json b/adapters/bmtm/brightmountainmediatest/params/race/video.json deleted file mode 100644 index efafd65f027..00000000000 --- a/adapters/bmtm/brightmountainmediatest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placement_id": 329 -} \ No newline at end of file diff --git a/adapters/brightroll/brightrolltest/params/race/banner.json b/adapters/brightroll/brightrolltest/params/race/banner.json deleted file mode 100644 index 6eb4ec6a337..00000000000 --- a/adapters/brightroll/brightrolltest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "publisher": "adthrive" -} diff --git a/adapters/brightroll/brightrolltest/params/race/video.json b/adapters/brightroll/brightrolltest/params/race/video.json deleted file mode 100644 index 6eb4ec6a337..00000000000 --- a/adapters/brightroll/brightrolltest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "publisher": "adthrive" -} diff --git a/adapters/colossus/colossustest/params/banner.json b/adapters/colossus/colossustest/params/banner.json deleted file mode 100644 index 7c2643d4901..00000000000 --- a/adapters/colossus/colossustest/params/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "61317" -} diff --git a/adapters/colossus/colossustest/params/race/banner.json b/adapters/colossus/colossustest/params/race/banner.json deleted file mode 100644 index 7c2643d4901..00000000000 --- a/adapters/colossus/colossustest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "61317" -} diff --git a/adapters/colossus/colossustest/params/race/video.json b/adapters/colossus/colossustest/params/race/video.json deleted file mode 100644 index 56f865c71d9..00000000000 --- a/adapters/colossus/colossustest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "61318" -} diff --git a/adapters/colossus/colossustest/params/video.json b/adapters/colossus/colossustest/params/video.json deleted file mode 100644 index 56f865c71d9..00000000000 --- a/adapters/colossus/colossustest/params/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "61318" -} diff --git a/adapters/connectad/connectadtest/params/race/banner.json b/adapters/connectad/connectadtest/params/race/banner.json deleted file mode 100644 index 01ae72064e2..00000000000 --- a/adapters/connectad/connectadtest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "siteId": "123456", - "networkId": "123456" - } - \ No newline at end of file diff --git a/adapters/conversant/conversanttest/params/race/banner.json b/adapters/conversant/conversanttest/params/race/banner.json deleted file mode 100644 index 0f2e61d99b7..00000000000 --- a/adapters/conversant/conversanttest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "site_id": "108060" -} diff --git a/adapters/conversant/conversanttest/params/race/video.json b/adapters/conversant/conversanttest/params/race/video.json deleted file mode 100644 index 5695507225f..00000000000 --- a/adapters/conversant/conversanttest/params/race/video.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "site_id": "88563", - "api": [2], - "protocols": [1, 2], - "mimes": ["video/mp4"], - "maxduration": 5000 -} diff --git a/adapters/datablocks/datablockstest/params/race/banner.json b/adapters/datablocks/datablockstest/params/race/banner.json deleted file mode 100644 index 0433de8388d..00000000000 --- a/adapters/datablocks/datablockstest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "sourceId": 906295, - "host": "search.nutella.datablocks.net" -} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/params/race/native.json b/adapters/datablocks/datablockstest/params/race/native.json deleted file mode 100644 index 0433de8388d..00000000000 --- a/adapters/datablocks/datablockstest/params/race/native.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "sourceId": 906295, - "host": "search.nutella.datablocks.net" -} \ No newline at end of file diff --git a/adapters/datablocks/datablockstest/params/race/video.json b/adapters/datablocks/datablockstest/params/race/video.json deleted file mode 100644 index 0433de8388d..00000000000 --- a/adapters/datablocks/datablockstest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "sourceId": 906295, - "host": "search.nutella.datablocks.net" -} \ No newline at end of file diff --git a/adapters/decenterads/decenteradstest/params/race/banner.json b/adapters/decenterads/decenteradstest/params/race/banner.json deleted file mode 100644 index dbdac1ad995..00000000000 --- a/adapters/decenterads/decenteradstest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "6" -} \ No newline at end of file diff --git a/adapters/decenterads/decenteradstest/params/race/video.json b/adapters/decenterads/decenteradstest/params/race/video.json deleted file mode 100644 index 6e2e0b3803b..00000000000 --- a/adapters/decenterads/decenteradstest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "7" -} \ No newline at end of file diff --git a/adapters/deepintent/deepintenttest/params/banner.json b/adapters/deepintent/deepintenttest/params/banner.json deleted file mode 100644 index 1323b625fed..00000000000 --- a/adapters/deepintent/deepintenttest/params/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "tagId": "16" -} \ No newline at end of file diff --git a/adapters/deepintent/deepintenttest/params/race/banner.json b/adapters/deepintent/deepintenttest/params/race/banner.json deleted file mode 100644 index 1323b625fed..00000000000 --- a/adapters/deepintent/deepintenttest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "tagId": "16" -} \ No newline at end of file diff --git a/adapters/dmx/dmxtest/params/race/banner.json b/adapters/dmx/dmxtest/params/race/banner.json deleted file mode 100644 index 1c0adff78ac..00000000000 --- a/adapters/dmx/dmxtest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "tagid": "25251", - "publisher_id": "100152" -} \ No newline at end of file diff --git a/adapters/dmx/dmxtest/params/race/video.json b/adapters/dmx/dmxtest/params/race/video.json deleted file mode 100644 index 3bbd83bd3b0..00000000000 --- a/adapters/dmx/dmxtest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "tagid": "25255", - "publisher_id": "100151" -} \ 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 deleted file mode 100644 index 0a04c95b072..00000000000 --- a/adapters/e_volution/evolutiontest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "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 deleted file mode 100644 index 032b9dd56d8..00000000000 --- a/adapters/e_volution/evolutiontest/params/race/native.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": "test_native" -} diff --git a/adapters/e_volution/evolutiontest/params/race/video.json b/adapters/e_volution/evolutiontest/params/race/video.json deleted file mode 100644 index 87071003920..00000000000 --- a/adapters/e_volution/evolutiontest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": "test_video" -} diff --git a/adapters/emx_digital/emx_digitaltest/params/race/banner.json b/adapters/emx_digital/emx_digitaltest/params/race/banner.json deleted file mode 100644 index 5155c92d11e..00000000000 --- a/adapters/emx_digital/emx_digitaltest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "tagid": "25251", - "bidfloor": "0.01" -} diff --git a/adapters/emx_digital/emx_digitaltest/params/race/video.json b/adapters/emx_digital/emx_digitaltest/params/race/video.json deleted file mode 100644 index d3e484af6b1..00000000000 --- a/adapters/emx_digital/emx_digitaltest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "tagid": "25251", - "bidfloor": "0.01" -} diff --git a/adapters/engagebdr/engagebdrtest/params/race/banner.json b/adapters/engagebdr/engagebdrtest/params/race/banner.json deleted file mode 100644 index 55128cf449f..00000000000 --- a/adapters/engagebdr/engagebdrtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zoneid": "99999" -} diff --git a/adapters/engagebdr/engagebdrtest/params/race/native.json b/adapters/engagebdr/engagebdrtest/params/race/native.json deleted file mode 100644 index 7df27654325..00000000000 --- a/adapters/engagebdr/engagebdrtest/params/race/native.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zoneid": "99997" -} diff --git a/adapters/engagebdr/engagebdrtest/params/race/video.json b/adapters/engagebdr/engagebdrtest/params/race/video.json deleted file mode 100644 index b60e65203bf..00000000000 --- a/adapters/engagebdr/engagebdrtest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zoneid": "99998" -} diff --git a/adapters/eplanning/eplanningtest/params/race/banner.json b/adapters/eplanning/eplanningtest/params/race/banner.json deleted file mode 100644 index dda08c7d7e4..00000000000 --- a/adapters/eplanning/eplanningtest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "ci": "18f66", - "adunit_code": "TestAdUnit" -} diff --git a/adapters/epom/epomtest/params/race/banner.json b/adapters/epom/epomtest/params/race/banner.json deleted file mode 100644 index 2c63c085104..00000000000 --- a/adapters/epom/epomtest/params/race/banner.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} diff --git a/adapters/epom/epomtest/params/race/native.json b/adapters/epom/epomtest/params/race/native.json deleted file mode 100644 index 7a73a41bfdf..00000000000 --- a/adapters/epom/epomtest/params/race/native.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/adapters/epom/epomtest/params/race/video.json b/adapters/epom/epomtest/params/race/video.json deleted file mode 100644 index 2c63c085104..00000000000 --- a/adapters/epom/epomtest/params/race/video.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} diff --git a/adapters/gamma/gammatest/params/race/banner.json b/adapters/gamma/gammatest/params/race/banner.json deleted file mode 100644 index a0bacc2b792..00000000000 --- a/adapters/gamma/gammatest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "sample-id", - "zid": "sample-zone-id", - "wid": "sample-web-id" -} diff --git a/adapters/gamma/gammatest/params/race/video.json b/adapters/gamma/gammatest/params/race/video.json deleted file mode 100644 index a0bacc2b792..00000000000 --- a/adapters/gamma/gammatest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "sample-id", - "zid": "sample-zone-id", - "wid": "sample-web-id" -} diff --git a/adapters/gamoshi/gamoshitest/params/race/banner.json b/adapters/gamoshi/gamoshitest/params/race/banner.json deleted file mode 100644 index 985a6040d93..00000000000 --- a/adapters/gamoshi/gamoshitest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "supplyPartnerId": "1707" -} diff --git a/adapters/gamoshi/gamoshitest/params/race/video.json b/adapters/gamoshi/gamoshitest/params/race/video.json deleted file mode 100644 index 985a6040d93..00000000000 --- a/adapters/gamoshi/gamoshitest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "supplyPartnerId": "1707" -} diff --git a/adapters/grid/gridtest/params/race/banner.json b/adapters/grid/gridtest/params/race/banner.json deleted file mode 100644 index f64051bd563..00000000000 --- a/adapters/grid/gridtest/params/race/banner.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "uid": 1, - "keywords": { - "site": { - "somePublisher": [ - { - "name": "someName", - "topic": ["stress", "fear"] - } - ] - } - } -} - diff --git a/adapters/gumgum/gumgumtest/params/race/banner.json b/adapters/gumgum/gumgumtest/params/race/banner.json deleted file mode 100644 index 6e222304f36..00000000000 --- a/adapters/gumgum/gumgumtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zone": "dc9d6be1" -} diff --git a/adapters/gumgum/gumgumtest/params/race/video.json b/adapters/gumgum/gumgumtest/params/race/video.json deleted file mode 100644 index 3ed284384d3..00000000000 --- a/adapters/gumgum/gumgumtest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zone": "dc9d6be1" -} \ No newline at end of file diff --git a/adapters/improvedigital/improvedigitaltest/params/race/banner.json b/adapters/improvedigital/improvedigitaltest/params/race/banner.json deleted file mode 100644 index 0de1d580215..00000000000 --- a/adapters/improvedigital/improvedigitaltest/params/race/banner.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "placementId": 123, - "publisherId": 321, - "placementKey": "uniq_name", - "size": { - "w": 100, - "h": 100 - }, - "keyValues": { - "testKey1": ["testValueA", "testValueB"] - } -} diff --git a/adapters/improvedigital/improvedigitaltest/params/race/native.json b/adapters/improvedigital/improvedigitaltest/params/race/native.json deleted file mode 100644 index 0de1d580215..00000000000 --- a/adapters/improvedigital/improvedigitaltest/params/race/native.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "placementId": 123, - "publisherId": 321, - "placementKey": "uniq_name", - "size": { - "w": 100, - "h": 100 - }, - "keyValues": { - "testKey1": ["testValueA", "testValueB"] - } -} diff --git a/adapters/improvedigital/improvedigitaltest/params/race/video.json b/adapters/improvedigital/improvedigitaltest/params/race/video.json deleted file mode 100644 index 0de1d580215..00000000000 --- a/adapters/improvedigital/improvedigitaltest/params/race/video.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "placementId": 123, - "publisherId": 321, - "placementKey": "uniq_name", - "size": { - "w": 100, - "h": 100 - }, - "keyValues": { - "testKey1": ["testValueA", "testValueB"] - } -} diff --git a/adapters/inmobi/inmobitest/params/race/banner.json b/adapters/inmobi/inmobitest/params/race/banner.json deleted file mode 100644 index 7791393fc99..00000000000 --- a/adapters/inmobi/inmobitest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "plc": "1596825400965" -} diff --git a/adapters/inmobi/inmobitest/params/race/video.json b/adapters/inmobi/inmobitest/params/race/video.json deleted file mode 100644 index 74a44b6e6f9..00000000000 --- a/adapters/inmobi/inmobitest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "plc": "1598991608990" -} diff --git a/adapters/interactiveoffers/interactiveofferstest/params/race/banner.json b/adapters/interactiveoffers/interactiveofferstest/params/race/banner.json deleted file mode 100644 index d81c02a5dc3..00000000000 --- a/adapters/interactiveoffers/interactiveofferstest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "pubid": 35 -} diff --git a/adapters/invibes/invibestest/params/race/banner.json b/adapters/invibes/invibestest/params/race/banner.json deleted file mode 100644 index 270daf0cf73..00000000000 --- a/adapters/invibes/invibestest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placementId": "placement-123" -} diff --git a/adapters/ix/ixtest/params/race/audio.json b/adapters/ix/ixtest/params/race/audio.json deleted file mode 100644 index 703df52e0dc..00000000000 --- a/adapters/ix/ixtest/params/race/audio.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "siteId": "569749", - "size": [300, 250] -} diff --git a/adapters/ix/ixtest/params/race/banner.json b/adapters/ix/ixtest/params/race/banner.json deleted file mode 100644 index 703df52e0dc..00000000000 --- a/adapters/ix/ixtest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "siteId": "569749", - "size": [300, 250] -} diff --git a/adapters/ix/ixtest/params/race/native.json b/adapters/ix/ixtest/params/race/native.json deleted file mode 100644 index 703df52e0dc..00000000000 --- a/adapters/ix/ixtest/params/race/native.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "siteId": "569749", - "size": [300, 250] -} diff --git a/adapters/ix/ixtest/params/race/video.json b/adapters/ix/ixtest/params/race/video.json deleted file mode 100644 index 993f0f6a4fb..00000000000 --- a/adapters/ix/ixtest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "siteId": "569749", - "size": [940, 560] -} diff --git a/adapters/jixie/jixietest/params/race/banner.json b/adapters/jixie/jixietest/params/race/banner.json deleted file mode 100644 index a0523d34c3f..00000000000 --- a/adapters/jixie/jixietest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "unit": "1000008-AbCdEf123400", - "bidfloor": "0.02" -} diff --git a/adapters/jixie/jixietest/params/race/video.json b/adapters/jixie/jixietest/params/race/video.json deleted file mode 100644 index b6d11e8fc4e..00000000000 --- a/adapters/jixie/jixietest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "unit": "1000008-AbCdEf2345", - "bidfloor": "0.03" -} diff --git a/adapters/kayzen/kayzentest/params/race/banner.json b/adapters/kayzen/kayzentest/params/race/banner.json deleted file mode 100644 index bdb2c9de976..00000000000 --- a/adapters/kayzen/kayzentest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "zone": "dc", - "exchange": "ex" -} - diff --git a/adapters/kayzen/kayzentest/params/race/native.json b/adapters/kayzen/kayzentest/params/race/native.json deleted file mode 100644 index bdb2c9de976..00000000000 --- a/adapters/kayzen/kayzentest/params/race/native.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "zone": "dc", - "exchange": "ex" -} - diff --git a/adapters/kayzen/kayzentest/params/race/video.json b/adapters/kayzen/kayzentest/params/race/video.json deleted file mode 100644 index bdb2c9de976..00000000000 --- a/adapters/kayzen/kayzentest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "zone": "dc", - "exchange": "ex" -} - diff --git a/adapters/krushmedia/krushmediatest/params/race/banner.json b/adapters/krushmedia/krushmediatest/params/race/banner.json deleted file mode 100644 index 8f4ee2f5025..00000000000 --- a/adapters/krushmedia/krushmediatest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": "2" -} \ No newline at end of file diff --git a/adapters/krushmedia/krushmediatest/params/race/native.json b/adapters/krushmedia/krushmediatest/params/race/native.json deleted file mode 100644 index 3c55c54e6bc..00000000000 --- a/adapters/krushmedia/krushmediatest/params/race/native.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": "2" -} diff --git a/adapters/krushmedia/krushmediatest/params/race/video.json b/adapters/krushmedia/krushmediatest/params/race/video.json deleted file mode 100644 index 3c55c54e6bc..00000000000 --- a/adapters/krushmedia/krushmediatest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": "2" -} diff --git a/adapters/lockerdome/lockerdometest/params/race/banner.json b/adapters/lockerdome/lockerdometest/params/race/banner.json deleted file mode 100644 index acd8a7de87d..00000000000 --- a/adapters/lockerdome/lockerdometest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "adUnitId": "LD9434769725128805" -} diff --git a/adapters/logicad/logicadtest/params/race/banner.json b/adapters/logicad/logicadtest/params/race/banner.json deleted file mode 100644 index 7cb3de5a1ef..00000000000 --- a/adapters/logicad/logicadtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "tid": "testtid" -} \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/params/race/banner.json b/adapters/lunamedia/lunamediatest/params/race/banner.json deleted file mode 100644 index 2eed8f2ec4e..00000000000 --- a/adapters/lunamedia/lunamediatest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" -} \ No newline at end of file diff --git a/adapters/lunamedia/lunamediatest/params/race/video.json b/adapters/lunamedia/lunamediatest/params/race/video.json deleted file mode 100644 index 2eed8f2ec4e..00000000000 --- a/adapters/lunamedia/lunamediatest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" -} \ No newline at end of file diff --git a/adapters/madvertise/madvertisetest/params/race/banner.json b/adapters/madvertise/madvertisetest/params/race/banner.json deleted file mode 100644 index 45243896f71..00000000000 --- a/adapters/madvertise/madvertisetest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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 deleted file mode 100644 index 8d462a8e8d7..00000000000 --- a/adapters/madvertise/madvertisetest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zoneId": "/1111111/video" -} \ No newline at end of file diff --git a/adapters/marsmedia/marsmediatest/params/race/banner.json b/adapters/marsmedia/marsmediatest/params/race/banner.json deleted file mode 100644 index 3a7279576be..00000000000 --- a/adapters/marsmedia/marsmediatest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zone": "9999" -} diff --git a/adapters/marsmedia/marsmediatest/params/race/video.json b/adapters/marsmedia/marsmediatest/params/race/video.json deleted file mode 100644 index 3a7279576be..00000000000 --- a/adapters/marsmedia/marsmediatest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zone": "9999" -} diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json deleted file mode 100644 index 3ffcd9bf63c..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "6" -} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json deleted file mode 100644 index 3ce815613d1..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": "6" -} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json deleted file mode 100644 index 1e42cfc4a05..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "7" -} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json deleted file mode 100644 index 8c4421b65ef..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": "7" -} \ No newline at end of file diff --git a/adapters/mobilefuse/mobilefusetest/params/race/banner.json b/adapters/mobilefuse/mobilefusetest/params/race/banner.json deleted file mode 100644 index 8c8d3f2b90c..00000000000 --- a/adapters/mobilefuse/mobilefusetest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "placement_id": 123456, - "pub_id": 1234, - "tagid_src": "ext" -} diff --git a/adapters/mobilefuse/mobilefusetest/params/race/video.json b/adapters/mobilefuse/mobilefusetest/params/race/video.json deleted file mode 100644 index 8c8d3f2b90c..00000000000 --- a/adapters/mobilefuse/mobilefusetest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "placement_id": 123456, - "pub_id": 1234, - "tagid_src": "ext" -} diff --git a/adapters/nanointeractive/nanointeractivetest/params/race/banner.json b/adapters/nanointeractive/nanointeractivetest/params/race/banner.json deleted file mode 100644 index bb35ea8488a..00000000000 --- a/adapters/nanointeractive/nanointeractivetest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "pid": "58bfec94eb0a1916fa380163" -} diff --git a/adapters/ninthdecimal/ninthdecimaltest/params/race/banner.json b/adapters/ninthdecimal/ninthdecimaltest/params/race/banner.json deleted file mode 100644 index 2eed8f2ec4e..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" -} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/params/race/video.json b/adapters/ninthdecimal/ninthdecimaltest/params/race/video.json deleted file mode 100644 index 2eed8f2ec4e..00000000000 --- a/adapters/ninthdecimal/ninthdecimaltest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", - "placement": "dummyplacement" -} \ No newline at end of file diff --git a/adapters/onetag/onetagtest/params/race/banner.json b/adapters/onetag/onetagtest/params/race/banner.json deleted file mode 100644 index 687438d6be6..00000000000 --- a/adapters/onetag/onetagtest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubId": "386276e072", - "ext": {} -} \ No newline at end of file diff --git a/adapters/onetag/onetagtest/params/race/native.json b/adapters/onetag/onetagtest/params/race/native.json deleted file mode 100644 index 687438d6be6..00000000000 --- a/adapters/onetag/onetagtest/params/race/native.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubId": "386276e072", - "ext": {} -} \ No newline at end of file diff --git a/adapters/onetag/onetagtest/params/race/video.json b/adapters/onetag/onetagtest/params/race/video.json deleted file mode 100644 index 687438d6be6..00000000000 --- a/adapters/onetag/onetagtest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pubId": "386276e072", - "ext": {} -} \ No newline at end of file diff --git a/adapters/openx/openxtest/params/race/banner.json b/adapters/openx/openxtest/params/race/banner.json deleted file mode 100644 index b53005da6c1..00000000000 --- a/adapters/openx/openxtest/params/race/banner.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "unit": "539439964", - "delDomain": "se-demo-d.openx.net", - "customFloor": 0.1, - "customParams": {"foo": "bar"} -} diff --git a/adapters/openx/openxtest/params/race/video.json b/adapters/openx/openxtest/params/race/video.json deleted file mode 100644 index b53005da6c1..00000000000 --- a/adapters/openx/openxtest/params/race/video.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "unit": "539439964", - "delDomain": "se-demo-d.openx.net", - "customFloor": 0.1, - "customParams": {"foo": "bar"} -} diff --git a/adapters/orbidder/orbiddertest/params/race/banner.json b/adapters/orbidder/orbiddertest/params/race/banner.json deleted file mode 100644 index bdb0e010e05..00000000000 --- a/adapters/orbidder/orbiddertest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "accountId": "orbidder-test", - "placementId": "center-banner", - "bidfloor": 0.1 -} \ No newline at end of file diff --git a/adapters/outbrain/outbraintest/params/race/banner.json b/adapters/outbrain/outbraintest/params/race/banner.json deleted file mode 100644 index 05577a78e8b..00000000000 --- a/adapters/outbrain/outbraintest/params/race/banner.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "publisher": { - "id": "publisher-id", - "name": "publisher-name", - "domain": "publisher-domain.com" - }, - "tagid": "tag-id", - "bcat": ["bad-category"], - "badv": ["bad-advertiser"] -} \ No newline at end of file diff --git a/adapters/outbrain/outbraintest/params/race/native.json b/adapters/outbrain/outbraintest/params/race/native.json deleted file mode 100644 index 05577a78e8b..00000000000 --- a/adapters/outbrain/outbraintest/params/race/native.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "publisher": { - "id": "publisher-id", - "name": "publisher-name", - "domain": "publisher-domain.com" - }, - "tagid": "tag-id", - "bcat": ["bad-category"], - "badv": ["bad-advertiser"] -} \ No newline at end of file diff --git a/adapters/pangle/pangletest/params/race/banner.json b/adapters/pangle/pangletest/params/race/banner.json deleted file mode 100644 index 4a9da04cf8e..00000000000 --- a/adapters/pangle/pangletest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 4a9da04cf8e..00000000000 --- a/adapters/pangle/pangletest/params/race/native.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 4a9da04cf8e..00000000000 --- a/adapters/pangle/pangletest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "token": "123", - "appid": "123", - "placementid": "123" -} \ No newline at end of file diff --git a/adapters/pubmatic/pubmatictest/params/race/banner.json b/adapters/pubmatic/pubmatictest/params/race/banner.json deleted file mode 100644 index ab88871edf4..00000000000 --- a/adapters/pubmatic/pubmatictest/params/race/banner.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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", - "preference": "sports,movies" - }, - "wrapper": { - "version": 2, - "profile": 595 - } -} diff --git a/adapters/pubmatic/pubmatictest/params/race/video.json b/adapters/pubmatic/pubmatictest/params/race/video.json deleted file mode 100644 index ab88871edf4..00000000000 --- a/adapters/pubmatic/pubmatictest/params/race/video.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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", - "preference": "sports,movies" - }, - "wrapper": { - "version": 2, - "profile": 595 - } -} diff --git a/adapters/pulsepoint/pulsepointtest/params/race/banner.json b/adapters/pulsepoint/pulsepointtest/params/race/banner.json deleted file mode 100644 index bc3e9371a5f..00000000000 --- a/adapters/pulsepoint/pulsepointtest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cp": 512379, - "ct": 486653 -} diff --git a/adapters/pulsepoint/pulsepointtest/params/race/native.json b/adapters/pulsepoint/pulsepointtest/params/race/native.json deleted file mode 100644 index 57867bc27c0..00000000000 --- a/adapters/pulsepoint/pulsepointtest/params/race/native.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cp": 512379, - "ct": 486655 -} diff --git a/adapters/pulsepoint/pulsepointtest/params/race/video.json b/adapters/pulsepoint/pulsepointtest/params/race/video.json deleted file mode 100644 index d8ef37eb793..00000000000 --- a/adapters/pulsepoint/pulsepointtest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "cp": 512379, - "ct": 486654 -} diff --git a/adapters/rtbhouse/rtbhousetest/params/race/banner.json b/adapters/rtbhouse/rtbhousetest/params/race/banner.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/adapters/rtbhouse/rtbhousetest/params/race/banner.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/adapters/rubicon/rubicontest/params/race/banner.json b/adapters/rubicon/rubicontest/params/race/banner.json deleted file mode 100644 index 9a698379b23..00000000000 --- a/adapters/rubicon/rubicontest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 -} diff --git a/adapters/rubicon/rubicontest/params/race/video.json b/adapters/rubicon/rubicontest/params/race/video.json deleted file mode 100644 index 9a698379b23..00000000000 --- a/adapters/rubicon/rubicontest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "accountId": 1001, - "siteId": 113932, - "zoneId": 535510 -} diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/banner.json b/adapters/sa_lunamedia/salunamediatest/params/race/banner.json deleted file mode 100644 index c02a5de97a2..00000000000 --- a/adapters/sa_lunamedia/salunamediatest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "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 deleted file mode 100644 index c02a5de97a2..00000000000 --- a/adapters/sa_lunamedia/salunamediatest/params/race/native.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "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 deleted file mode 100644 index c02a5de97a2..00000000000 --- a/adapters/sa_lunamedia/salunamediatest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key": "test" -} \ No newline at end of file diff --git a/adapters/sharethrough/sharethroughtest/params/race/banner.json b/adapters/sharethrough/sharethroughtest/params/race/banner.json deleted file mode 100644 index 6702f4c2965..00000000000 --- a/adapters/sharethrough/sharethroughtest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "pkey": "abc123", - "iframe": true, - "iframeSize": [50, 50] -} \ No newline at end of file diff --git a/adapters/sharethrough/sharethroughtest/params/race/native.json b/adapters/sharethrough/sharethroughtest/params/race/native.json deleted file mode 100644 index 6702f4c2965..00000000000 --- a/adapters/sharethrough/sharethroughtest/params/race/native.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "pkey": "abc123", - "iframe": true, - "iframeSize": [50, 50] -} \ No newline at end of file diff --git a/adapters/silvermob/silvermobtest/params/race/banner.json b/adapters/silvermob/silvermobtest/params/race/banner.json deleted file mode 100644 index 9b6ca9d749b..00000000000 --- a/adapters/silvermob/silvermobtest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "zoneid": "0", - "host": "eu" -} diff --git a/adapters/silvermob/silvermobtest/params/race/native.json b/adapters/silvermob/silvermobtest/params/race/native.json deleted file mode 100644 index f63a4842b6d..00000000000 --- a/adapters/silvermob/silvermobtest/params/race/native.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "zoneid": "0", - "host": "eu" -} \ No newline at end of file diff --git a/adapters/silvermob/silvermobtest/params/race/video.json b/adapters/silvermob/silvermobtest/params/race/video.json deleted file mode 100644 index f63a4842b6d..00000000000 --- a/adapters/silvermob/silvermobtest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "zoneid": "0", - "host": "eu" -} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/params/race/banner.json b/adapters/smaato/smaatotest/params/race/banner.json deleted file mode 100644 index a84c44d4d8e..00000000000 --- a/adapters/smaato/smaatotest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "publisherId": "1100042525", - "adspaceId": "130563103" -} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/params/race/video.json b/adapters/smaato/smaatotest/params/race/video.json deleted file mode 100644 index a84c44d4d8e..00000000000 --- a/adapters/smaato/smaatotest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "publisherId": "1100042525", - "adspaceId": "130563103" -} \ No newline at end of file diff --git a/adapters/smartadserver/smartadservertest/params/race/banner.json b/adapters/smartadserver/smartadservertest/params/race/banner.json deleted file mode 100644 index b34088307d4..00000000000 --- a/adapters/smartadserver/smartadservertest/params/race/banner.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "siteId": 1, - "pageId": 2, - "formatId": 3, - "networkId": 73 - } - \ No newline at end of file diff --git a/adapters/smartadserver/smartadservertest/params/race/video.json b/adapters/smartadserver/smartadservertest/params/race/video.json deleted file mode 100644 index b34088307d4..00000000000 --- a/adapters/smartadserver/smartadservertest/params/race/video.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "siteId": 1, - "pageId": 2, - "formatId": 3, - "networkId": 73 - } - \ No newline at end of file diff --git a/adapters/smarthub/smarthubtest/params/race/banner.json b/adapters/smarthub/smarthubtest/params/race/banner.json deleted file mode 100644 index 8c05702c6b2..00000000000 --- a/adapters/smarthub/smarthubtest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "partnerName": "partnertest", - "seat": "9Q20EdGxzgWdfPYShScl", - "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" -} diff --git a/adapters/smarthub/smarthubtest/params/race/native.json b/adapters/smarthub/smarthubtest/params/race/native.json deleted file mode 100644 index 8c05702c6b2..00000000000 --- a/adapters/smarthub/smarthubtest/params/race/native.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "partnerName": "partnertest", - "seat": "9Q20EdGxzgWdfPYShScl", - "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" -} diff --git a/adapters/smarthub/smarthubtest/params/race/video.json b/adapters/smarthub/smarthubtest/params/race/video.json deleted file mode 100644 index 8c05702c6b2..00000000000 --- a/adapters/smarthub/smarthubtest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "partnerName": "partnertest", - "seat": "9Q20EdGxzgWdfPYShScl", - "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" -} diff --git a/adapters/smartrtb/smartrtbtest/params/race/banner.json b/adapters/smartrtb/smartrtbtest/params/race/banner.json deleted file mode 100644 index 207a504539f..00000000000 --- a/adapters/smartrtb/smartrtbtest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "pub_id": "test", - "zone_id": "N4zTDq3PPEHBIODv7cXK", - "force_bid": true -} \ No newline at end of file diff --git a/adapters/smartrtb/smartrtbtest/params/race/video.json b/adapters/smartrtb/smartrtbtest/params/race/video.json deleted file mode 100644 index 207a504539f..00000000000 --- a/adapters/smartrtb/smartrtbtest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "pub_id": "test", - "zone_id": "N4zTDq3PPEHBIODv7cXK", - "force_bid": true -} \ No newline at end of file diff --git a/adapters/smartyads/smartyadstest/params/race/banner.json b/adapters/smartyads/smartyadstest/params/race/banner.json deleted file mode 100644 index 571c3b374b6..00000000000 --- a/adapters/smartyads/smartyadstest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "host": "ep1", - "sourceid": "partner", - "accountid": "hash" -} diff --git a/adapters/smartyads/smartyadstest/params/race/native.json b/adapters/smartyads/smartyadstest/params/race/native.json deleted file mode 100644 index 3d04df6cb08..00000000000 --- a/adapters/smartyads/smartyadstest/params/race/native.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "host": "ep1", - "sourceid": "partner", - "accountid": "hash" -} \ No newline at end of file diff --git a/adapters/smartyads/smartyadstest/params/race/video.json b/adapters/smartyads/smartyadstest/params/race/video.json deleted file mode 100644 index 3d04df6cb08..00000000000 --- a/adapters/smartyads/smartyadstest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "host": "ep1", - "sourceid": "partner", - "accountid": "hash" -} \ No newline at end of file diff --git a/adapters/smilewanted/smilewantedtest/params/race/banner.json b/adapters/smilewanted/smilewantedtest/params/race/banner.json deleted file mode 100644 index 42dddd702a0..00000000000 --- a/adapters/smilewanted/smilewantedtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zoneId": "zone_code_test_display" -} diff --git a/adapters/smilewanted/smilewantedtest/params/race/video.json b/adapters/smilewanted/smilewantedtest/params/race/video.json deleted file mode 100644 index 64ac780ecde..00000000000 --- a/adapters/smilewanted/smilewantedtest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "zoneId": "zone_code_test_video" -} diff --git a/adapters/somoaudience/somoaudiencetest/params/race/banner.json b/adapters/somoaudience/somoaudiencetest/params/race/banner.json deleted file mode 100644 index 4fe8830bd60..00000000000 --- a/adapters/somoaudience/somoaudiencetest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8", "bid_floor": 1.05 -} diff --git a/adapters/somoaudience/somoaudiencetest/params/race/video.json b/adapters/somoaudience/somoaudiencetest/params/race/video.json deleted file mode 100644 index c8b8ae54c04..00000000000 --- a/adapters/somoaudience/somoaudiencetest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ -"placement_hash": "22a58cfb0c9b656bff713d1236e930e8", "bid_floor":1.05 -} diff --git a/adapters/sonobi/sonobitest/params/race/banner.json b/adapters/sonobi/sonobitest/params/race/banner.json deleted file mode 100644 index 693a62c6561..00000000000 --- a/adapters/sonobi/sonobitest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "123" -} \ No newline at end of file diff --git a/adapters/sonobi/sonobitest/params/race/video.json b/adapters/sonobi/sonobitest/params/race/video.json deleted file mode 100644 index 693a62c6561..00000000000 --- a/adapters/sonobi/sonobitest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "123" -} \ No newline at end of file diff --git a/adapters/sovrn/sovrntest/params/banner.json b/adapters/sovrn/sovrntest/params/banner.json deleted file mode 100644 index c921b82b3b8..00000000000 --- a/adapters/sovrn/sovrntest/params/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "tagId": "403370" -} diff --git a/adapters/synacormedia/synacormediatest/params/banner.json b/adapters/synacormedia/synacormediatest/params/banner.json deleted file mode 100644 index bb55ddfc48c..00000000000 --- a/adapters/synacormedia/synacormediatest/params/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "seatId": "123", - "tagId": "234" -} diff --git a/adapters/synacormedia/synacormediatest/params/video.json b/adapters/synacormedia/synacormediatest/params/video.json deleted file mode 100644 index bb55ddfc48c..00000000000 --- a/adapters/synacormedia/synacormediatest/params/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "seatId": "123", - "tagId": "234" -} diff --git a/adapters/tappx/tappxtest/params/race/banner.json b/adapters/tappx/tappxtest/params/race/banner.json deleted file mode 100644 index 9264443a5ca..00000000000 --- a/adapters/tappx/tappxtest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tappxkey": "pub-12345-android-9876", - "endpoint": "ZZ123456PS", - "host": "testing.ssp.tappx.com/rtb/v2/" -} \ No newline at end of file diff --git a/adapters/tappx/tappxtest/params/race/video.json b/adapters/tappx/tappxtest/params/race/video.json deleted file mode 100644 index 438543f2362..00000000000 --- a/adapters/tappx/tappxtest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tappxkey": "pub-12345-android-9876", - "endpoint": "VZ123456PS", - "host": "testing.ssp.tappx.com/rtb/v2/" -} \ No newline at end of file diff --git a/adapters/telaria/telariatest/params/race/video.json b/adapters/telaria/telariatest/params/race/video.json deleted file mode 100644 index e3b67ec8c20..00000000000 --- a/adapters/telaria/telariatest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "adCode": "my-adcode", - "seatCode": "my-seatcode" -} diff --git a/adapters/ucfunnel/ucfunneltest/params/race/banner.json b/adapters/ucfunnel/ucfunneltest/params/race/banner.json deleted file mode 100644 index 2c8c2e1e198..00000000000 --- a/adapters/ucfunnel/ucfunneltest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "adunitid": "ad-83444226E44368D1E32E49EEBE6D29", - "partnerid": "par-2EDDB423AA24474188B843EE4842932" -} - \ No newline at end of file diff --git a/adapters/ucfunnel/ucfunneltest/params/race/video.json b/adapters/ucfunnel/ucfunneltest/params/race/video.json deleted file mode 100644 index 0a562b34aa1..00000000000 --- a/adapters/ucfunnel/ucfunneltest/params/race/video.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "adunitid": "ad-E2B22B678D6A664E092824848D26BB2", - "partnerid": "par-2EDDB423AA24474188B843EE4842932" -} - \ No newline at end of file diff --git a/adapters/unicorn/unicorntest/params/race/banner.json b/adapters/unicorn/unicorntest/params/race/banner.json deleted file mode 100644 index 668983e3d19..00000000000 --- a/adapters/unicorn/unicorntest/params/race/banner.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "accountId": 199578, - "publisherId": 123456, - "mediaId": "test_media", - "placementId": "test_placement" -} diff --git a/adapters/unruly/unrulytest/params/race/video.json b/adapters/unruly/unrulytest/params/race/video.json deleted file mode 100644 index f0b1b20ad90..00000000000 --- a/adapters/unruly/unrulytest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "uuid": 1234, - "siteid": 1234 -} diff --git a/adapters/verizonmedia/verizonmediatest/params/race/banner.json b/adapters/verizonmedia/verizonmediatest/params/race/banner.json deleted file mode 100644 index 739ec3c024b..00000000000 --- a/adapters/verizonmedia/verizonmediatest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "dcn": "2345", - "pos": "header" -} diff --git a/adapters/visx/visxtest/params/race/banner.json b/adapters/visx/visxtest/params/race/banner.json deleted file mode 100644 index b1c2d85a60e..00000000000 --- a/adapters/visx/visxtest/params/race/banner.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "uid": 7, - "size": [300, 250], - "ga_parms": { - "place_on_page": "ATF" - } -} diff --git a/adapters/yeahmobi/yeahmobitest/params/race/banner.json b/adapters/yeahmobi/yeahmobitest/params/race/banner.json deleted file mode 100644 index 7dc4c43515e..00000000000 --- a/adapters/yeahmobi/yeahmobitest/params/race/banner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "pubId": "222", - "zoneId": "sin" - } - \ No newline at end of file diff --git a/adapters/yieldone/yieldonetest/params/race/banner.json b/adapters/yieldone/yieldonetest/params/race/banner.json deleted file mode 100644 index c88180845eb..00000000000 --- a/adapters/yieldone/yieldonetest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "placementId": "36891" -} - diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json deleted file mode 100644 index cff0af83143..00000000000 --- a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "sourceId": 906295, - "host": "q.0cf.io" -} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json deleted file mode 100644 index cff0af83143..00000000000 --- a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/native.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "sourceId": 906295, - "host": "q.0cf.io" -} \ No newline at end of file diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json b/adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json deleted file mode 100644 index cff0af83143..00000000000 --- a/adapters/zeroclickfraud/zeroclickfraudtest/params/race/video.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "sourceId": 906295, - "host": "q.0cf.io" -} \ No newline at end of file diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index aa4443529ad..ea8a7b84c88 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1789,7 +1789,7 @@ func TestRaceIntegration(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) auctionRequest := AuctionRequest{ - BidRequest: newRaceCheckingRequest(t), + BidRequest: getTestBuildRequest(t), Account: config.Account{}, UserSyncs: &emptyUsersync{}, } @@ -1814,9 +1814,7 @@ func newCategoryFetcher(directory string) (stored_requests.CategoryFetcher, erro return catfetcher, nil } -// newRaceCheckingRequest builds a BidRequest from all the params in the -// adapters/{bidder}/{bidder}test/params/race/*.json files -func newRaceCheckingRequest(t *testing.T) *openrtb2.BidRequest { +func getTestBuildRequest(t *testing.T) *openrtb2.BidRequest { dnt := int8(1) return &openrtb2.BidRequest{ Site: &openrtb2.Site{ @@ -1856,7 +1854,7 @@ func newRaceCheckingRequest(t *testing.T) *openrtb2.BidRequest { H: 600, }}, }, - Ext: buildImpExt(t, "banner"), + Ext: json.RawMessage(`{"ext_field":"value}"}`), }, { Video: &openrtb2.Video{ MIMEs: []string{"video/mp4"}, @@ -1865,7 +1863,7 @@ func newRaceCheckingRequest(t *testing.T) *openrtb2.BidRequest { W: 300, H: 600, }, - Ext: buildImpExt(t, "video"), + Ext: json.RawMessage(`{"ext_field":"value}"}`), }}, } } @@ -1927,30 +1925,7 @@ func TestPanicRecovery(t *testing.T) { recovered(bidderRequests[0], nil) } -func buildImpExt(t *testing.T, jsonFilename string) json.RawMessage { - adapterFolders, err := ioutil.ReadDir("../adapters") - if err != nil { - t.Fatalf("Failed to open adapters directory: %v", err) - } - bidderExts := make(map[string]json.RawMessage) - for _, adapterFolder := range adapterFolders { - if adapterFolder.IsDir() && adapterFolder.Name() != "adapterstest" { - bidderName := adapterFolder.Name() - sampleParams := "../adapters/" + bidderName + "/" + bidderName + "test/params/race/" + jsonFilename + ".json" - // If the file doesn't exist, don't worry about it. I don't think the Go APIs offer a reliable way to check for this. - fileContents, err := ioutil.ReadFile(sampleParams) - if err == nil { - bidderExts[bidderName] = json.RawMessage(fileContents) - } - } - } - toReturn, err := json.Marshal(bidderExts) - if err != nil { - t.Fatalf("Failed to marshal JSON: %v", err) - } - return json.RawMessage(toReturn) -} - +// TestPanicRecoveryHighLevel calls HoldAuction with a panicingAdapter{} func TestPanicRecoveryHighLevel(t *testing.T) { noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) @@ -2014,7 +1989,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { H: 600, }}, }, - Ext: buildImpExt(t, "banner"), + Ext: json.RawMessage(`{"ext_field": "value"}`), }}, } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 1d13928b59c..a0812548ed5 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -452,7 +452,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { consentedVendors map[string]bool }{ { - req: AuctionRequest{BidRequest: newRaceCheckingRequest(t), UserSyncs: &emptyUsersync{}}, + req: AuctionRequest{BidRequest: getTestBuildRequest(t), UserSyncs: &emptyUsersync{}}, bidReqAssertions: assertReq, hasError: false, applyCOPPA: true, From a35b0d01905f4a9c103f5425dbc7942774b6b7ce Mon Sep 17 00:00:00 2001 From: IQZoneAdx <88879712+IQZoneAdx@users.noreply.github.com> Date: Thu, 19 Aug 2021 20:33:28 +0300 Subject: [PATCH 065/140] New Adapter: IQZone (#1964) --- adapters/iqzone/iqzone.go | 99 +++++++++++++ adapters/iqzone/iqzone_test.go | 20 +++ .../iqzonetest/exemplary/simple-banner.json | 132 ++++++++++++++++++ .../iqzonetest/exemplary/simple-native.json | 116 +++++++++++++++ .../iqzonetest/exemplary/simple-video.json | 127 +++++++++++++++++ .../exemplary/simple-web-banner.json | 132 ++++++++++++++++++ .../supplemental/bad_media_type.json | 85 +++++++++++ .../iqzonetest/supplemental/bad_response.json | 83 +++++++++++ .../iqzonetest/supplemental/status-204.json | 78 +++++++++++ .../supplemental/status-not-200.json | 83 +++++++++++ adapters/iqzone/params_test.go | 45 ++++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_iqzone.go | 5 + static/bidder-info/iqzone.yaml | 15 ++ static/bidder-params/iqzone.json | 15 ++ usersync/usersyncers/syncer_test.go | 1 + 18 files changed, 1041 insertions(+) create mode 100644 adapters/iqzone/iqzone.go create mode 100644 adapters/iqzone/iqzone_test.go create mode 100644 adapters/iqzone/iqzonetest/exemplary/simple-banner.json create mode 100644 adapters/iqzone/iqzonetest/exemplary/simple-native.json create mode 100644 adapters/iqzone/iqzonetest/exemplary/simple-video.json create mode 100644 adapters/iqzone/iqzonetest/exemplary/simple-web-banner.json create mode 100644 adapters/iqzone/iqzonetest/supplemental/bad_media_type.json create mode 100644 adapters/iqzone/iqzonetest/supplemental/bad_response.json create mode 100644 adapters/iqzone/iqzonetest/supplemental/status-204.json create mode 100644 adapters/iqzone/iqzonetest/supplemental/status-not-200.json create mode 100644 adapters/iqzone/params_test.go create mode 100644 openrtb_ext/imp_iqzone.go create mode 100644 static/bidder-info/iqzone.yaml create mode 100644 static/bidder-params/iqzone.json diff --git a/adapters/iqzone/iqzone.go b/adapters/iqzone/iqzone.go new file mode 100644 index 00000000000..190466b36e0 --- /dev/null +++ b/adapters/iqzone/iqzone.go @@ -0,0 +1,99 @@ +package iqzone + +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 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) { + 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.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 { + bidType, err := getMediaTypeForImp(seatBid.Bid[i].ImpID, request.Imp) + if err != nil { + return nil, []error{err} + } + + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + var mediaType openrtb_ext.BidType = "" + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner != nil { + mediaType = openrtb_ext.BidTypeBanner + return mediaType, nil + } + if imp.Banner == nil && imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + return mediaType, nil + } + if imp.Banner == nil && imp.Video == nil && imp.Native != nil { + mediaType = openrtb_ext.BidTypeNative + return mediaType, nil + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression \"%s\"", impID), + } +} diff --git a/adapters/iqzone/iqzone_test.go b/adapters/iqzone/iqzone_test.go new file mode 100644 index 00000000000..69ee77e2775 --- /dev/null +++ b/adapters/iqzone/iqzone_test.go @@ -0,0 +1,20 @@ +package iqzone + +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.BidderIQZone, config.Adapter{ + Endpoint: "http://smartssp-us-east.iqzone.com/pserver"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "iqzonetest", bidder) +} diff --git a/adapters/iqzone/iqzonetest/exemplary/simple-banner.json b/adapters/iqzone/iqzonetest/exemplary/simple-banner.json new file mode 100644 index 00000000000..f816191ec32 --- /dev/null +++ b/adapters/iqzone/iqzonetest/exemplary/simple-banner.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://smartssp-us-east.iqzone.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "iqzone" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/iqzone/iqzonetest/exemplary/simple-native.json b/adapters/iqzone/iqzonetest/exemplary/simple-native.json new file mode 100644 index 00000000000..819e14bf572 --- /dev/null +++ b/adapters/iqzone/iqzonetest/exemplary/simple-native.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "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": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://smartssp-us-east.iqzone.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "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": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "iqzone" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/iqzone/iqzonetest/exemplary/simple-video.json b/adapters/iqzone/iqzonetest/exemplary/simple-video.json new file mode 100644 index 00000000000..16e6f4c7144 --- /dev/null +++ b/adapters/iqzone/iqzonetest/exemplary/simple-video.json @@ -0,0 +1,127 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://smartssp-us-east.iqzone.com/pserver", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "iqzone" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/iqzone/iqzonetest/exemplary/simple-web-banner.json b/adapters/iqzone/iqzonetest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..968c030575b --- /dev/null +++ b/adapters/iqzone/iqzonetest/exemplary/simple-web-banner.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://smartssp-us-east.iqzone.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "iqzone" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/iqzone/iqzonetest/supplemental/bad_media_type.json b/adapters/iqzone/iqzonetest/supplemental/bad_media_type.json new file mode 100644 index 00000000000..3d1923104e7 --- /dev/null +++ b/adapters/iqzone/iqzonetest/supplemental/bad_media_type.json @@ -0,0 +1,85 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://smartssp-us-east.iqzone.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "iqzone" + } + ], + "cur": "USD" + } + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression \"test-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/iqzone/iqzonetest/supplemental/bad_response.json b/adapters/iqzone/iqzonetest/supplemental/bad_response.json new file mode 100644 index 00000000000..ad420705fcd --- /dev/null +++ b/adapters/iqzone/iqzonetest/supplemental/bad_response.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://smartssp-us-east.iqzone.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/iqzone/iqzonetest/supplemental/status-204.json b/adapters/iqzone/iqzonetest/supplemental/status-204.json new file mode 100644 index 00000000000..7c2f21c0aa7 --- /dev/null +++ b/adapters/iqzone/iqzonetest/supplemental/status-204.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://smartssp-us-east.iqzone.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }], + "expectedBidResponses": [] +} diff --git a/adapters/iqzone/iqzonetest/supplemental/status-not-200.json b/adapters/iqzone/iqzonetest/supplemental/status-not-200.json new file mode 100644 index 00000000000..22160ac78d3 --- /dev/null +++ b/adapters/iqzone/iqzonetest/supplemental/status-not-200.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://smartssp-us-east.iqzone.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 404, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 404. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/iqzone/params_test.go b/adapters/iqzone/params_test.go new file mode 100644 index 00000000000..deb4af2eda6 --- /dev/null +++ b/adapters/iqzone/params_test.go @@ -0,0 +1,45 @@ +package iqzone + +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 schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderIQZone, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderIQZone, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"placementId": "test"}`, + `{"placementId": "1"}`, +} + +var invalidParams = []string{ + `{"placementId": 42}`, + `{}`, + `{"someOtherParam": "value"}`, +} diff --git a/config/config.go b/config/config.go index 865c422ffb8..224cf85931b 100644 --- a/config/config.go +++ b/config/config.go @@ -917,6 +917,7 @@ func SetupViper(v *viper.Viper, filename string) { 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.iqzone.endpoint", "http://smartssp-us-east.iqzone.com/pserver") 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.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 7a61a914357..b25cff8c40e 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -62,6 +62,7 @@ import ( "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/iqzone" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/jixie" "github.com/prebid/prebid-server/adapters/kayzen" @@ -189,6 +190,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderInMobi: inmobi.Builder, openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder, openrtb_ext.BidderInvibes: invibes.Builder, + openrtb_ext.BidderIQZone: iqzone.Builder, openrtb_ext.BidderIx: ix.Builder, openrtb_ext.BidderJixie: jixie.Builder, openrtb_ext.BidderKayzen: kayzen.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 8e74fcc6db8..79d9abda16e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -133,6 +133,7 @@ const ( BidderInMobi BidderName = "inmobi" BidderInteractiveoffers BidderName = "interactiveoffers" BidderInvibes BidderName = "invibes" + BidderIQZone BidderName = "iqzone" BidderIx BidderName = "ix" BidderJixie BidderName = "jixie" BidderKayzen BidderName = "kayzen" @@ -260,6 +261,7 @@ func CoreBidderNames() []BidderName { BidderInMobi, BidderInteractiveoffers, BidderInvibes, + BidderIQZone, BidderIx, BidderJixie, BidderKayzen, diff --git a/openrtb_ext/imp_iqzone.go b/openrtb_ext/imp_iqzone.go new file mode 100644 index 00000000000..67a03376067 --- /dev/null +++ b/openrtb_ext/imp_iqzone.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtIQZone struct { + PlacementID string `json:"placementId"` +} diff --git a/static/bidder-info/iqzone.yaml b/static/bidder-info/iqzone.yaml new file mode 100644 index 00000000000..254caa8c6e3 --- /dev/null +++ b/static/bidder-info/iqzone.yaml @@ -0,0 +1,15 @@ +maintainer: + email: "smartssp@iqzone.com" +capabilities: + site: + mediaTypes: + - banner + - video + - native + + app: + mediaTypes: + - banner + - video + - native + diff --git a/static/bidder-params/iqzone.json b/static/bidder-params/iqzone.json new file mode 100644 index 00000000000..38e98c53346 --- /dev/null +++ b/static/bidder-params/iqzone.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "IQZone Adapter Params", + "description": "A schema which validates params accepted by the IQZone adapter", + "type": "object", + + "properties": { + "placementId": { + "type": "string", + "description": "Placement ID" + } + }, + + "required": ["placementId"] + } \ No newline at end of file diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 7eacd086ac3..4a6ffb96c99 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -126,6 +126,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderEpom: true, openrtb_ext.BidderDecenterAds: true, openrtb_ext.BidderInteractiveoffers: true, + openrtb_ext.BidderIQZone: true, openrtb_ext.BidderKayzen: true, openrtb_ext.BidderKidoz: true, openrtb_ext.BidderKubient: true, From 6e0b3de84d91f65cdddda3942e7cc732f2f6029a Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 19 Aug 2021 15:39:00 -0400 Subject: [PATCH 066/140] /cookie_sync Endpoint Rewrite (#1879) --- adapters/33across/usersync.go | 12 - adapters/33across/usersync_test.go | 34 - adapters/acuityads/acuityads.go | 4 +- adapters/acuityads/usersync.go | 12 - adapters/acuityads/usersync_test.go | 33 - adapters/adagio/usersync.go | 12 - adapters/adagio/usersync_test.go | 34 - adapters/adf/usersync.go | 12 - adapters/adf/usersync_test.go | 31 - adapters/adform/adform_test.go | 2 +- adapters/adform/usersync.go | 12 - adapters/adform/usersync_test.go | 31 - adapters/adhese/adhese.go | 4 +- adapters/adkernel/adkernel.go | 4 +- adapters/adkernel/usersync.go | 12 - adapters/adkernel/usersync_test.go | 34 - adapters/adkernelAdn/adkernelAdn.go | 4 +- adapters/adkernelAdn/usersync.go | 12 - adapters/adkernelAdn/usersync_test.go | 34 - adapters/adman/usersync.go | 13 - adapters/adman/usersync_test.go | 34 - adapters/admixer/usersync.go | 12 - adapters/admixer/usersync_test.go | 35 - adapters/adocean/adocean.go | 4 +- adapters/adocean/usersync.go | 12 - adapters/adocean/usersync_test.go | 33 - adapters/adoppler/adoppler.go | 2 +- adapters/adpone/usersync.go | 18 - adapters/adpone/usersync_test.go | 23 - adapters/adtarget/usersync.go | 12 - adapters/adtarget/usersync_test.go | 37 - adapters/adtelligent/usersync.go | 12 - adapters/adtelligent/usersync_test.go | 29 - adapters/advangelists/advangelists.go | 4 +- adapters/advangelists/usersync.go | 12 - adapters/advangelists/usersync_test.go | 30 - adapters/adxcg/usersync.go | 12 - adapters/adxcg/usersync_test.go | 29 - adapters/adyoulike/usersync.go | 12 - adapters/adyoulike/usersync_test.go | 35 - adapters/aja/usersync.go | 12 - adapters/aja/usersync_test.go | 35 - adapters/algorix/algorix.go | 4 +- adapters/amx/usersync.go | 13 - adapters/amx/usersync_test.go | 22 - adapters/appnexus/appnexus_test.go | 2 +- adapters/appnexus/usersync.go | 12 - adapters/appnexus/usersync_test.go | 24 - adapters/audienceNetwork/usersync.go | 12 - adapters/audienceNetwork/usersync_test.go | 24 - adapters/avocet/usersync.go | 12 - adapters/avocet/usersync_test.go | 34 - adapters/axonix/axonix.go | 4 +- adapters/beachfront/usersync.go | 15 - adapters/beachfront/usersync_test.go | 34 - adapters/beintoo/usersync.go | 12 - adapters/beintoo/usersync_test.go | 34 - adapters/between/between.go | 4 +- adapters/between/usersync.go | 13 - adapters/between/usersync_test.go | 24 - adapters/bidmachine/bidmachine.go | 4 +- adapters/bidmyadz/usersync.go | 12 - adapters/bidmyadz/usersync_test.go | 33 - adapters/bmtm/usersync.go | 12 - adapters/bmtm/usersync_test.go | 29 - adapters/brightroll/usersync.go | 12 - adapters/brightroll/usersync_test.go | 24 - adapters/colossus/usersync.go | 13 - adapters/colossus/usersync_test.go | 34 - adapters/connectad/usersync.go | 12 - adapters/connectad/usersync_test.go | 34 - adapters/consumable/usersync.go | 15 - adapters/consumable/usersync_test.go | 34 - adapters/conversant/cnvr_legacy_test.go | 2 +- adapters/conversant/usersync.go | 12 - adapters/conversant/usersync_test.go | 29 - adapters/cpmstar/usersync.go | 13 - adapters/cpmstar/usersync_test.go | 24 - adapters/criteo/usersync.go | 14 - adapters/criteo/usersync_test.go | 26 - adapters/datablocks/datablocks.go | 4 +- adapters/datablocks/usersync.go | 12 - adapters/datablocks/usersync_test.go | 34 - adapters/deepintent/usersync.go | 13 - adapters/deepintent/usersync_test.go | 34 - adapters/dmx/usersync.go | 12 - adapters/dmx/usersync_test.go | 20 - adapters/e_volution/usersync.go | 12 - adapters/e_volution/usersync_test.go | 33 - adapters/emx_digital/usersync.go | 12 - adapters/emx_digital/usersync_test.go | 34 - adapters/engagebdr/usersync.go | 12 - adapters/engagebdr/usersync_test.go | 34 - adapters/eplanning/usersync.go | 12 - adapters/eplanning/usersync_test.go | 24 - adapters/gamma/usersync.go | 12 - adapters/gamma/usersync_test.go | 29 - adapters/gamoshi/usersync.go | 12 - adapters/gamoshi/usersync_test.go | 29 - adapters/grid/usersync.go | 12 - adapters/grid/usersync_test.go | 29 - adapters/gumgum/usersync.go | 12 - adapters/gumgum/usersync_test.go | 34 - adapters/improvedigital/usersync.go | 12 - adapters/improvedigital/usersync_test.go | 34 - adapters/inmobi/usersync.go | 12 - adapters/invibes/invibes.go | 4 +- adapters/invibes/usersync.go | 16 - adapters/invibes/usersync_test.go | 31 - adapters/ix/usersync.go | 12 - adapters/ix/usersync_test.go | 24 - adapters/jixie/usersync.go | 12 - adapters/jixie/usersync_test.go | 24 - adapters/kayzen/kayzen.go | 4 +- adapters/krushmedia/krushmedia.go | 4 +- adapters/krushmedia/usersync.go | 12 - adapters/krushmedia/usersync_test.go | 33 - adapters/lockerdome/usersync.go | 12 - adapters/lockerdome/usersync_test.go | 24 - adapters/logicad/usersync.go | 12 - adapters/logicad/usersync_test.go | 30 - adapters/lunamedia/lunamedia.go | 4 +- adapters/lunamedia/usersync.go | 12 - adapters/lunamedia/usersync_test.go | 30 - adapters/madvertise/madvertise.go | 4 +- adapters/marsmedia/usersync.go | 12 - adapters/marsmedia/usersync_test.go | 35 - adapters/mediafuse/usersync.go | 12 - adapters/mediafuse/usersync_test.go | 29 - adapters/mgid/usersync.go | 12 - adapters/mgid/usersync_test.go | 29 - adapters/mobilefuse/mobilefuse.go | 4 +- adapters/nanointeractive/usersync.go | 12 - adapters/nanointeractive/usersync_test.go | 35 - adapters/ninthdecimal/ninthdecimal.go | 4 +- adapters/ninthdecimal/usersync.go | 12 - adapters/ninthdecimal/usersync_test.go | 30 - adapters/nobid/usersync.go | 12 - adapters/nobid/usersync_test.go | 34 - adapters/onetag/onetag.go | 4 +- adapters/onetag/usersync.go | 12 - adapters/onetag/usersync_test.go | 23 - adapters/openrtb_util_test.go | 2 +- adapters/openx/usersync.go | 12 - adapters/openx/usersync_test.go | 24 - adapters/operaads/operaads.go | 7 +- adapters/operaads/usersync.go | 11 - adapters/operaads/usersync_test.go | 33 - adapters/outbrain/usersync.go | 12 - adapters/outbrain/usersync_test.go | 33 - adapters/pubmatic/pubmatic_test.go | 2 +- adapters/pubmatic/usersync.go | 12 - adapters/pubmatic/usersync_test.go | 34 - adapters/pulsepoint/pulsepoint_test.go | 2 +- adapters/pulsepoint/usersync.go | 12 - adapters/pulsepoint/usersync_test.go | 24 - adapters/rhythmone/usersync.go | 12 - adapters/rhythmone/usersync_test.go | 34 - adapters/rtbhouse/usersync.go | 18 - adapters/rtbhouse/usersync_test.go | 29 - adapters/rubicon/rubicon_test.go | 24 +- adapters/rubicon/usersync.go | 12 - adapters/rubicon/usersync_test.go | 30 - adapters/sa_lunamedia/usersync.go | 12 - adapters/sa_lunamedia/usersync_test.go | 33 - adapters/sharethrough/usersync.go | 12 - adapters/sharethrough/usersync_test.go | 30 - adapters/silvermob/silvermob.go | 4 +- adapters/smartadserver/usersync.go | 12 - adapters/smartadserver/usersync_test.go | 34 - adapters/smarthub/smarthub.go | 4 +- adapters/smarthub/usersync.go | 12 - adapters/smarthub/usersync_test.go | 33 - adapters/smartrtb/smartrtb.go | 4 +- adapters/smartrtb/usersync.go | 12 - adapters/smartrtb/usersync_test.go | 19 - adapters/smartyads/smartyads.go | 4 +- adapters/smartyads/usersync.go | 12 - adapters/smartyads/usersync_test.go | 33 - adapters/smilewanted/usersync.go | 12 - adapters/smilewanted/usersync_test.go | 34 - adapters/somoaudience/usersync.go | 12 - adapters/somoaudience/usersync_test.go | 24 - adapters/sonobi/usersync.go | 12 - adapters/sonobi/usersync_test.go | 29 - adapters/sovrn/sovrn_test.go | 2 +- adapters/sovrn/usersync.go | 12 - adapters/sovrn/usersync_test.go | 29 - adapters/synacormedia/synacormedia.go | 4 +- adapters/synacormedia/usersync.go | 12 - adapters/synacormedia/usersync_test.go | 24 - adapters/syncer.go | 51 - adapters/syncer_test.go | 36 - adapters/tappx/tappx.go | 4 +- adapters/tappx/usersync.go | 12 - adapters/tappx/usersync_test.go | 34 - adapters/telaria/usersync.go | 12 - adapters/telaria/usersync_test.go | 32 - adapters/triplelift/usersync.go | 12 - adapters/triplelift/usersync_test.go | 24 - adapters/triplelift_native/usersync.go | 12 - adapters/triplelift_native/usersync_test.go | 24 - adapters/trustx/usersync.go | 12 - adapters/trustx/usersync_test.go | 29 - adapters/ucfunnel/usersync.go | 12 - adapters/ucfunnel/usersync_test.go | 29 - adapters/unruly/usersync.go | 12 - adapters/unruly/usersync_test.go | 34 - adapters/valueimpression/usersync.go | 12 - adapters/valueimpression/usersync_test.go | 34 - adapters/verizonmedia/usersync.go | 12 - adapters/verizonmedia/usersync_test.go | 22 - adapters/viewdeos/usersync.go | 12 - adapters/viewdeos/usersync_test.go | 29 - adapters/visx/usersync.go | 12 - adapters/visx/usersync_test.go | 34 - adapters/vrtcal/usersync.go | 12 - adapters/vrtcal/usersync_test.go | 30 - adapters/yeahmobi/yeahmobi.go | 4 +- adapters/yieldlab/usersync.go | 12 - adapters/yieldlab/usersync_test.go | 25 - adapters/yieldmo/usersync.go | 12 - adapters/yieldmo/usersync_test.go | 29 - adapters/yieldone/usersync.go | 12 - adapters/yieldone/usersync_test.go | 29 - adapters/zeroclickfraud/usersync.go | 12 - adapters/zeroclickfraud/usersync_test.go | 34 - adapters/zeroclickfraud/zeroclickfraud.go | 4 +- analytics/core.go | 15 +- analytics/filesystem/file_module_test.go | 7 +- analytics/pubstack/helpers/json_test.go | 3 +- config/adapter.go | 87 +- config/bidderinfo.go | 177 ++- config/bidderinfo_test.go | 231 ++- config/bidderinfo_validate_test.go | 59 +- config/config.go | 156 +- config/config_test.go | 29 - config/test/bidder-info/someBidder.yaml | 14 + config/usersync.go | 14 + endpoints/auction.go | 46 +- endpoints/auction_test.go | 10 +- endpoints/cookie_sync.go | 477 +++--- endpoints/cookie_sync_test.go | 1425 +++++++++++++++--- endpoints/events/event.go | 29 +- endpoints/getuids.go | 2 +- endpoints/httprouterhandler.go | 11 + endpoints/openrtb2/amp_auction.go | 17 +- endpoints/openrtb2/amp_auction_test.go | 39 +- endpoints/openrtb2/auction.go | 8 +- endpoints/openrtb2/auction_benchmark_test.go | 14 +- endpoints/openrtb2/auction_test.go | 59 +- endpoints/openrtb2/video_auction.go | 8 +- endpoints/openrtb2/video_auction_test.go | 16 +- endpoints/setuid.go | 127 +- endpoints/setuid_test.go | 643 ++++---- exchange/exchange.go | 26 +- exchange/exchange_test.go | 59 +- exchange/utils.go | 11 +- exchange/utils_test.go | 22 +- gdpr/gdpr.go | 26 +- gdpr/gdpr_test.go | 53 - gdpr/impl.go | 39 +- gdpr/impl_test.go | 85 +- gdpr/signal.go | 46 + gdpr/signal_test.go | 116 ++ macros/macros.go | 2 +- macros/macros_test.go | 32 +- metrics/config/metrics.go | 59 +- metrics/config/metrics_test.go | 9 +- metrics/go_metrics.go | 110 +- metrics/go_metrics_test.go | 133 +- metrics/metrics.go | 113 +- metrics/metrics_mock.go | 21 +- metrics/prometheus/preload.go | 33 +- metrics/prometheus/prometheus.go | 76 +- metrics/prometheus/prometheus_test.go | 206 ++- metrics/prometheus/type_conversion.go | 49 +- pbs/pbsrequest.go | 42 +- pbs/usersync.go | 4 +- router/router.go | 143 +- router/router_test.go | 172 +++ server/listener_test.go | 2 +- static/bidder-info/33across.yaml | 4 + static/bidder-info/acuityads.yaml | 5 +- static/bidder-info/adagio.yaml | 6 +- static/bidder-info/adf.yaml | 4 + static/bidder-info/adform.yaml | 4 + static/bidder-info/adkernel.yaml | 4 + static/bidder-info/adkernelAdn.yaml | 4 + static/bidder-info/adman.yaml | 6 +- static/bidder-info/admixer.yaml | 6 +- static/bidder-info/adocean.yaml | 5 + static/bidder-info/adpone.yaml | 4 + static/bidder-info/adtarget.yaml | 5 + static/bidder-info/adtelligent.yaml | 5 + static/bidder-info/advangelists.yaml | 6 +- static/bidder-info/adxcg.yaml | 5 + static/bidder-info/adyoulike.yaml | 4 + static/bidder-info/aja.yaml | 4 + static/bidder-info/amx.yaml | 6 +- static/bidder-info/appnexus.yaml | 5 + static/bidder-info/audienceNetwork.yaml | 4 + static/bidder-info/avocet.yaml | 4 + static/bidder-info/beachfront.yaml | 4 + static/bidder-info/beintoo.yaml | 5 + static/bidder-info/between.yaml | 6 +- static/bidder-info/bidmyadz.yaml | 6 +- static/bidder-info/bmtm.yaml | 5 + static/bidder-info/brightroll.yaml | 4 + static/bidder-info/colossus.yaml | 4 + static/bidder-info/connectad.yaml | 5 + static/bidder-info/consumable.yaml | 5 + static/bidder-info/conversant.yaml | 5 + static/bidder-info/cpmstar.yaml | 4 + static/bidder-info/criteo.yaml | 7 +- static/bidder-info/datablocks.yaml | 4 + static/bidder-info/deepintent.yaml | 4 + static/bidder-info/dmx.yaml | 5 + static/bidder-info/e_volution.yaml | 6 +- static/bidder-info/emx_digital.yaml | 6 +- static/bidder-info/engagebdr.yaml | 5 + static/bidder-info/eplanning.yaml | 4 + static/bidder-info/gamma.yaml | 5 + static/bidder-info/gamoshi.yaml | 4 + static/bidder-info/grid.yaml | 6 +- static/bidder-info/gumgum.yaml | 7 +- static/bidder-info/improvedigital.yaml | 4 + static/bidder-info/inmobi.yaml | 4 + static/bidder-info/invibes.yaml | 5 + static/bidder-info/ix.yaml | 5 + static/bidder-info/jixie.yaml | 4 + static/bidder-info/krushmedia.yaml | 6 +- static/bidder-info/lockerdome.yaml | 19 + static/bidder-info/logicad.yaml | 4 + static/bidder-info/lunamedia.yaml | 6 +- static/bidder-info/marsmedia.yaml | 4 + static/bidder-info/mediafuse.yaml | 5 + static/bidder-info/mgid.yaml | 4 + static/bidder-info/nanointeractive.yaml | 4 + static/bidder-info/ninthdecimal.yaml | 6 +- static/bidder-info/nobid.yaml | 4 + static/bidder-info/onetag.yaml | 6 +- static/bidder-info/openx.yaml | 4 + static/bidder-info/operaads.yaml | 6 +- static/bidder-info/outbrain.yaml | 4 + static/bidder-info/pubmatic.yaml | 5 + static/bidder-info/pulsepoint.yaml | 4 + static/bidder-info/rhythmone.yaml | 4 + static/bidder-info/rtbhouse.yaml | 5 + static/bidder-info/rubicon.yaml | 5 + static/bidder-info/sa_lunamedia.yaml | 6 +- static/bidder-info/sharethrough.yaml | 4 + static/bidder-info/smartadserver.yaml | 4 + static/bidder-info/smarthub.yaml | 5 + static/bidder-info/smartrtb.yaml | 4 + static/bidder-info/smartyads.yaml | 4 + static/bidder-info/smilewanted.yaml | 4 + static/bidder-info/somoaudience.yaml | 4 + static/bidder-info/sonobi.yaml | 4 + static/bidder-info/sovrn.yaml | 4 + static/bidder-info/synacormedia.yaml | 9 + static/bidder-info/tappx.yaml | 6 +- static/bidder-info/telaria.yaml | 4 + static/bidder-info/triplelift.yaml | 4 + static/bidder-info/triplelift_native.yaml | 4 + static/bidder-info/trustx.yaml | 4 + static/bidder-info/ucfunnel.yaml | 4 + static/bidder-info/unruly.yaml | 4 + static/bidder-info/valueimpression.yaml | 4 + static/bidder-info/verizonmedia.yaml | 5 + static/bidder-info/viewdeos.yaml | 5 + static/bidder-info/visx.yaml | 4 + static/bidder-info/vrtcal.yaml | 5 + static/bidder-info/yieldlab.yaml | 4 + static/bidder-info/yieldmo.yaml | 4 + static/bidder-info/yieldone.yaml | 4 + static/bidder-info/zeroclickfraud.yaml | 4 + usersync/bidderchooser.go | 60 + usersync/bidderchooser_test.go | 277 ++++ usersync/bidderfilter.go | 62 + usersync/bidderfilter_test.go | 104 ++ usersync/chooser.go | 163 ++ usersync/chooser_test.go | 402 +++++ usersync/cookie.go | 191 +-- usersync/cookie_test.go | 245 +-- usersync/shuffler.go | 15 + usersync/shuffler_test.go | 42 + usersync/syncer.go | 291 ++++ usersync/syncer_test.go | 754 +++++++++ usersync/syncersbuilder.go | 126 ++ usersync/syncersbuilder_test.go | 340 +++++ usersync/synctype.go | 56 + usersync/synctype_test.go | 98 ++ usersync/usersync.go | 29 - usersync/usersyncers/syncer.go | 221 --- usersync/usersyncers/syncer_test.go | 158 -- util/httputil/pixel.go | 16 + util/sliceutil/sliceutil.go | 14 + util/sliceutil/sliceutil_test.go | 70 + 399 files changed, 7393 insertions(+), 6456 deletions(-) delete mode 100644 adapters/33across/usersync.go delete mode 100644 adapters/33across/usersync_test.go delete mode 100644 adapters/acuityads/usersync.go delete mode 100644 adapters/acuityads/usersync_test.go delete mode 100644 adapters/adagio/usersync.go delete mode 100644 adapters/adagio/usersync_test.go delete mode 100644 adapters/adf/usersync.go delete mode 100644 adapters/adf/usersync_test.go delete mode 100644 adapters/adform/usersync.go delete mode 100644 adapters/adform/usersync_test.go delete mode 100644 adapters/adkernel/usersync.go delete mode 100644 adapters/adkernel/usersync_test.go delete mode 100644 adapters/adkernelAdn/usersync.go delete mode 100644 adapters/adkernelAdn/usersync_test.go delete mode 100644 adapters/adman/usersync.go delete mode 100644 adapters/adman/usersync_test.go delete mode 100644 adapters/admixer/usersync.go delete mode 100644 adapters/admixer/usersync_test.go delete mode 100644 adapters/adocean/usersync.go delete mode 100644 adapters/adocean/usersync_test.go delete mode 100644 adapters/adpone/usersync.go delete mode 100644 adapters/adpone/usersync_test.go delete mode 100644 adapters/adtarget/usersync.go delete mode 100644 adapters/adtarget/usersync_test.go delete mode 100644 adapters/adtelligent/usersync.go delete mode 100644 adapters/adtelligent/usersync_test.go delete mode 100644 adapters/advangelists/usersync.go delete mode 100644 adapters/advangelists/usersync_test.go delete mode 100644 adapters/adxcg/usersync.go delete mode 100644 adapters/adxcg/usersync_test.go delete mode 100644 adapters/adyoulike/usersync.go delete mode 100644 adapters/adyoulike/usersync_test.go delete mode 100644 adapters/aja/usersync.go delete mode 100644 adapters/aja/usersync_test.go delete mode 100644 adapters/amx/usersync.go delete mode 100644 adapters/amx/usersync_test.go delete mode 100644 adapters/appnexus/usersync.go delete mode 100644 adapters/appnexus/usersync_test.go delete mode 100644 adapters/audienceNetwork/usersync.go delete mode 100644 adapters/audienceNetwork/usersync_test.go delete mode 100644 adapters/avocet/usersync.go delete mode 100644 adapters/avocet/usersync_test.go delete mode 100644 adapters/beachfront/usersync.go delete mode 100644 adapters/beachfront/usersync_test.go delete mode 100644 adapters/beintoo/usersync.go delete mode 100644 adapters/beintoo/usersync_test.go delete mode 100644 adapters/between/usersync.go delete mode 100644 adapters/between/usersync_test.go delete mode 100644 adapters/bidmyadz/usersync.go delete mode 100644 adapters/bidmyadz/usersync_test.go delete mode 100644 adapters/bmtm/usersync.go delete mode 100644 adapters/bmtm/usersync_test.go delete mode 100644 adapters/brightroll/usersync.go delete mode 100644 adapters/brightroll/usersync_test.go delete mode 100644 adapters/colossus/usersync.go delete mode 100644 adapters/colossus/usersync_test.go delete mode 100644 adapters/connectad/usersync.go delete mode 100644 adapters/connectad/usersync_test.go delete mode 100644 adapters/consumable/usersync.go delete mode 100644 adapters/consumable/usersync_test.go delete mode 100644 adapters/conversant/usersync.go delete mode 100644 adapters/conversant/usersync_test.go delete mode 100644 adapters/cpmstar/usersync.go delete mode 100644 adapters/cpmstar/usersync_test.go delete mode 100644 adapters/criteo/usersync.go delete mode 100644 adapters/criteo/usersync_test.go delete mode 100644 adapters/datablocks/usersync.go delete mode 100644 adapters/datablocks/usersync_test.go delete mode 100644 adapters/deepintent/usersync.go delete mode 100644 adapters/deepintent/usersync_test.go delete mode 100644 adapters/dmx/usersync.go delete mode 100644 adapters/dmx/usersync_test.go delete mode 100644 adapters/e_volution/usersync.go delete mode 100644 adapters/e_volution/usersync_test.go delete mode 100644 adapters/emx_digital/usersync.go delete mode 100644 adapters/emx_digital/usersync_test.go delete mode 100644 adapters/engagebdr/usersync.go delete mode 100644 adapters/engagebdr/usersync_test.go delete mode 100644 adapters/eplanning/usersync.go delete mode 100644 adapters/eplanning/usersync_test.go delete mode 100644 adapters/gamma/usersync.go delete mode 100644 adapters/gamma/usersync_test.go delete mode 100644 adapters/gamoshi/usersync.go delete mode 100644 adapters/gamoshi/usersync_test.go delete mode 100644 adapters/grid/usersync.go delete mode 100644 adapters/grid/usersync_test.go delete mode 100644 adapters/gumgum/usersync.go delete mode 100644 adapters/gumgum/usersync_test.go delete mode 100644 adapters/improvedigital/usersync.go delete mode 100644 adapters/improvedigital/usersync_test.go delete mode 100644 adapters/inmobi/usersync.go delete mode 100644 adapters/invibes/usersync.go delete mode 100644 adapters/invibes/usersync_test.go delete mode 100644 adapters/ix/usersync.go delete mode 100644 adapters/ix/usersync_test.go delete mode 100644 adapters/jixie/usersync.go delete mode 100644 adapters/jixie/usersync_test.go delete mode 100644 adapters/krushmedia/usersync.go delete mode 100644 adapters/krushmedia/usersync_test.go delete mode 100644 adapters/lockerdome/usersync.go delete mode 100644 adapters/lockerdome/usersync_test.go delete mode 100644 adapters/logicad/usersync.go delete mode 100644 adapters/logicad/usersync_test.go delete mode 100644 adapters/lunamedia/usersync.go delete mode 100644 adapters/lunamedia/usersync_test.go delete mode 100644 adapters/marsmedia/usersync.go delete mode 100644 adapters/marsmedia/usersync_test.go delete mode 100644 adapters/mediafuse/usersync.go delete mode 100644 adapters/mediafuse/usersync_test.go delete mode 100644 adapters/mgid/usersync.go delete mode 100644 adapters/mgid/usersync_test.go delete mode 100644 adapters/nanointeractive/usersync.go delete mode 100644 adapters/nanointeractive/usersync_test.go delete mode 100755 adapters/ninthdecimal/usersync.go delete mode 100755 adapters/ninthdecimal/usersync_test.go delete mode 100644 adapters/nobid/usersync.go delete mode 100644 adapters/nobid/usersync_test.go delete mode 100644 adapters/onetag/usersync.go delete mode 100644 adapters/onetag/usersync_test.go delete mode 100644 adapters/openx/usersync.go delete mode 100644 adapters/openx/usersync_test.go delete mode 100644 adapters/operaads/usersync.go delete mode 100644 adapters/operaads/usersync_test.go delete mode 100644 adapters/outbrain/usersync.go delete mode 100644 adapters/outbrain/usersync_test.go delete mode 100644 adapters/pubmatic/usersync.go delete mode 100644 adapters/pubmatic/usersync_test.go delete mode 100644 adapters/pulsepoint/usersync.go delete mode 100644 adapters/pulsepoint/usersync_test.go delete mode 100644 adapters/rhythmone/usersync.go delete mode 100644 adapters/rhythmone/usersync_test.go delete mode 100644 adapters/rtbhouse/usersync.go delete mode 100644 adapters/rtbhouse/usersync_test.go delete mode 100644 adapters/rubicon/usersync.go delete mode 100644 adapters/rubicon/usersync_test.go delete mode 100644 adapters/sa_lunamedia/usersync.go delete mode 100644 adapters/sa_lunamedia/usersync_test.go delete mode 100644 adapters/sharethrough/usersync.go delete mode 100644 adapters/sharethrough/usersync_test.go delete mode 100644 adapters/smartadserver/usersync.go delete mode 100644 adapters/smartadserver/usersync_test.go delete mode 100644 adapters/smarthub/usersync.go delete mode 100644 adapters/smarthub/usersync_test.go delete mode 100644 adapters/smartrtb/usersync.go delete mode 100644 adapters/smartrtb/usersync_test.go delete mode 100644 adapters/smartyads/usersync.go delete mode 100644 adapters/smartyads/usersync_test.go delete mode 100644 adapters/smilewanted/usersync.go delete mode 100644 adapters/smilewanted/usersync_test.go delete mode 100644 adapters/somoaudience/usersync.go delete mode 100644 adapters/somoaudience/usersync_test.go delete mode 100644 adapters/sonobi/usersync.go delete mode 100644 adapters/sonobi/usersync_test.go delete mode 100644 adapters/sovrn/usersync.go delete mode 100644 adapters/sovrn/usersync_test.go delete mode 100644 adapters/synacormedia/usersync.go delete mode 100644 adapters/synacormedia/usersync_test.go delete mode 100644 adapters/syncer.go delete mode 100644 adapters/syncer_test.go delete mode 100644 adapters/tappx/usersync.go delete mode 100644 adapters/tappx/usersync_test.go delete mode 100644 adapters/telaria/usersync.go delete mode 100644 adapters/telaria/usersync_test.go delete mode 100644 adapters/triplelift/usersync.go delete mode 100644 adapters/triplelift/usersync_test.go delete mode 100644 adapters/triplelift_native/usersync.go delete mode 100644 adapters/triplelift_native/usersync_test.go delete mode 100644 adapters/trustx/usersync.go delete mode 100644 adapters/trustx/usersync_test.go delete mode 100644 adapters/ucfunnel/usersync.go delete mode 100644 adapters/ucfunnel/usersync_test.go delete mode 100644 adapters/unruly/usersync.go delete mode 100644 adapters/unruly/usersync_test.go delete mode 100644 adapters/valueimpression/usersync.go delete mode 100644 adapters/valueimpression/usersync_test.go delete mode 100644 adapters/verizonmedia/usersync.go delete mode 100644 adapters/verizonmedia/usersync_test.go delete mode 100644 adapters/viewdeos/usersync.go delete mode 100644 adapters/viewdeos/usersync_test.go delete mode 100644 adapters/visx/usersync.go delete mode 100644 adapters/visx/usersync_test.go delete mode 100644 adapters/vrtcal/usersync.go delete mode 100644 adapters/vrtcal/usersync_test.go delete mode 100644 adapters/yieldlab/usersync.go delete mode 100644 adapters/yieldlab/usersync_test.go delete mode 100644 adapters/yieldmo/usersync.go delete mode 100644 adapters/yieldmo/usersync_test.go delete mode 100644 adapters/yieldone/usersync.go delete mode 100644 adapters/yieldone/usersync_test.go delete mode 100644 adapters/zeroclickfraud/usersync.go delete mode 100644 adapters/zeroclickfraud/usersync_test.go create mode 100644 config/usersync.go create mode 100644 endpoints/httprouterhandler.go create mode 100644 gdpr/signal.go create mode 100644 gdpr/signal_test.go create mode 100644 usersync/bidderchooser.go create mode 100644 usersync/bidderchooser_test.go create mode 100644 usersync/bidderfilter.go create mode 100644 usersync/bidderfilter_test.go create mode 100644 usersync/chooser.go create mode 100644 usersync/chooser_test.go create mode 100644 usersync/shuffler.go create mode 100644 usersync/shuffler_test.go create mode 100644 usersync/syncer.go create mode 100644 usersync/syncer_test.go create mode 100644 usersync/syncersbuilder.go create mode 100644 usersync/syncersbuilder_test.go create mode 100644 usersync/synctype.go create mode 100644 usersync/synctype_test.go delete mode 100644 usersync/usersync.go delete mode 100644 usersync/usersyncers/syncer.go delete mode 100644 usersync/usersyncers/syncer_test.go create mode 100644 util/httputil/pixel.go create mode 100644 util/sliceutil/sliceutil.go create mode 100644 util/sliceutil/sliceutil_test.go diff --git a/adapters/33across/usersync.go b/adapters/33across/usersync.go deleted file mode 100644 index df26f3b6325..00000000000 --- a/adapters/33across/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package ttx - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func New33AcrossSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("33across", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/33across/usersync_test.go b/adapters/33across/usersync_test.go deleted file mode 100644 index e99b5965746..00000000000 --- a/adapters/33across/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package ttx - -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 Test33AcrossSyncer(t *testing.T) { - syncURL := "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru=%2Fsetuid%3Fbidder%3D33across%26uid%3D33XUSERID33X&id=zzz000000000002zzz" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := New33AcrossSyncer(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://ic.tynt.com/r/d?m=xch&rt=html&gdpr=A&gdpr_consent=B&us_privacy=C&ru=%2Fsetuid%3Fbidder%3D33across%26uid%3D33XUSERID33X&id=zzz000000000002zzz", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.False(t, syncInfo.SupportCORS) -} diff --git a/adapters/acuityads/acuityads.go b/adapters/acuityads/acuityads.go index da1cda3da77..84a754fe753 100644 --- a/adapters/acuityads/acuityads.go +++ b/adapters/acuityads/acuityads.go @@ -15,7 +15,7 @@ import ( ) type AcuityAdsAdapter struct { - endpoint template.Template + endpoint *template.Template } // Builder builds a new instance of the AcuityAds adapter for the given bidder with the given config. @@ -26,7 +26,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &AcuityAdsAdapter{ - endpoint: *template, + endpoint: template, } return bidder, nil } diff --git a/adapters/acuityads/usersync.go b/adapters/acuityads/usersync.go deleted file mode 100644 index e2fc1f41961..00000000000 --- a/adapters/acuityads/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package acuityads - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAcuityAdsSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("acuityads", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/acuityads/usersync_test.go b/adapters/acuityads/usersync_test.go deleted file mode 100644 index b3ad10bdbb8..00000000000 --- a/adapters/acuityads/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package acuityads - -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 TestAcuityAdsSyncer(t *testing.T) { - syncURL := "https://cs.admanmedia.com/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dacuityads%26uid%3D%5BUID%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - syncer := NewAcuityAdsSyncer(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://cs.admanmedia.com/sync/prebid?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dacuityads%26uid%3D%5BUID%5D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/adagio/usersync.go b/adapters/adagio/usersync.go deleted file mode 100644 index a7230feaada..00000000000 --- a/adapters/adagio/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index cd4195e16df..00000000000 --- a/adapters/adagio/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -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/adf/usersync.go b/adapters/adf/usersync.go deleted file mode 100644 index e3bd11422c4..00000000000 --- a/adapters/adf/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 693e6418444..00000000000 --- a/adapters/adf/usersync_test.go +++ /dev/null @@ -1,31 +0,0 @@ -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/adform_test.go b/adapters/adform/adform_test.go index 70cd6883a4d..53219f4c4c0 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -222,7 +222,7 @@ func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { prebidHttpRequest.Header.Add("Referer", adformTestData.referrer) prebidHttpRequest.Header.Add("X-Real-IP", adformTestData.deviceIP) - pbsCookie := usersync.ParsePBSCookieFromRequest(prebidHttpRequest, &config.HostCookie{}) + pbsCookie := usersync.ParseCookieFromRequest(prebidHttpRequest, &config.HostCookie{}) pbsCookie.TrySync("adform", adformTestData.buyerUID) fakeWriter := httptest.NewRecorder() diff --git a/adapters/adform/usersync.go b/adapters/adform/usersync.go deleted file mode 100644 index 6a237f794a6..00000000000 --- a/adapters/adform/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package adform - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdformSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adform", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/adform/usersync_test.go b/adapters/adform/usersync_test.go deleted file mode 100644 index f133be86583..00000000000 --- a/adapters/adform/usersync_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package adform - -import ( - "testing" - "text/template" - - "github.com/stretchr/testify/assert" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" -) - -func TestAdformSyncer(t *testing.T) { - syncURL := "//cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadform%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdformSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadform%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index 8b4d8c98afe..6fc12e3df5e 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -20,7 +20,7 @@ import ( ) type AdheseAdapter struct { - endpointTemplate template.Template + endpointTemplate *template.Template } func extractSlotParameter(parameters openrtb_ext.ExtImpAdhese) string { @@ -276,7 +276,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &AdheseAdapter{ - endpointTemplate: *template, + endpointTemplate: template, } return bidder, nil } diff --git a/adapters/adkernel/adkernel.go b/adapters/adkernel/adkernel.go index 362307cce79..b2ffb17f56a 100644 --- a/adapters/adkernel/adkernel.go +++ b/adapters/adkernel/adkernel.go @@ -16,7 +16,7 @@ import ( ) type adkernelAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } //MakeRequests prepares request information for prebid-server core @@ -262,7 +262,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &adkernelAdapter{ - EndpointTemplate: *urlTemplate, + EndpointTemplate: urlTemplate, } return bidder, nil } diff --git a/adapters/adkernel/usersync.go b/adapters/adkernel/usersync.go deleted file mode 100644 index 2de82a00f6e..00000000000 --- a/adapters/adkernel/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package adkernel - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdkernelSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adkernel", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/adkernel/usersync_test.go b/adapters/adkernel/usersync_test.go deleted file mode 100644 index 2ff81668d41..00000000000 --- a/adapters/adkernel/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package adkernel - -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 TestAdkernelAdnSyncer(t *testing.T) { - syncURL := "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdkernelSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://sync.adkernel.com/user-sync?t=image&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%7BUID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go index f6075f865e7..9ece01dba5e 100644 --- a/adapters/adkernelAdn/adkernelAdn.go +++ b/adapters/adkernelAdn/adkernelAdn.go @@ -18,7 +18,7 @@ import ( const defaultDomain string = "tag.adkernel.com" type adkernelAdnAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } //MakeRequests prepares request information for prebid-server core @@ -286,7 +286,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &adkernelAdnAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return bidder, nil } diff --git a/adapters/adkernelAdn/usersync.go b/adapters/adkernelAdn/usersync.go deleted file mode 100644 index 5a890e1565b..00000000000 --- a/adapters/adkernelAdn/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package adkernelAdn - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdkernelAdnSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adkernelAdn", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/adkernelAdn/usersync_test.go b/adapters/adkernelAdn/usersync_test.go deleted file mode 100644 index 17cf97ce779..00000000000 --- a/adapters/adkernelAdn/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package adkernelAdn - -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 TestAdkernelAdnSyncer(t *testing.T) { - syncURL := "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdkernelAdnSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://tag.adkernel.com/syncr?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%7BUID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/adman/usersync.go b/adapters/adman/usersync.go deleted file mode 100644 index 2cb62fe7824..00000000000 --- a/adapters/adman/usersync.go +++ /dev/null @@ -1,13 +0,0 @@ -package adman - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -// NewAdmanSyncer returns adman syncer -func NewAdmanSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adman", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/adman/usersync_test.go b/adapters/adman/usersync_test.go deleted file mode 100644 index d0dc90c8c5d..00000000000 --- a/adapters/adman/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package adman - -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 TestAdmanSyncer(t *testing.T) { - syncURL := "https://sync.admanmedia.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadman%26uid%3D%5BUID%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdmanSyncer(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://sync.admanmedia.com/pbs.gif?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadman%26uid%3D%5BUID%5D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/admixer/usersync.go b/adapters/admixer/usersync.go deleted file mode 100644 index 89e162dff32..00000000000 --- a/adapters/admixer/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package admixer - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdmixerSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("admixer", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/admixer/usersync_test.go b/adapters/admixer/usersync_test.go deleted file mode 100644 index 6acc48453fb..00000000000 --- a/adapters/admixer/usersync_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package admixer - -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 TestAdmixerSyncer(t *testing.T) { - syncURL := "http://anyHost/anyPath" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdmixerSyncer(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, "http://anyHost/anyPath", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index 6a0eb892be4..d13104b14d7 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -69,14 +69,14 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters whiteSpace := regexp.MustCompile(`\s+`) bidder := &AdOceanAdapter{ - endpointTemplate: *endpointTemplate, + endpointTemplate: endpointTemplate, measurementCode: whiteSpace.ReplaceAllString(measurementCode, " "), } return bidder, nil } type AdOceanAdapter struct { - endpointTemplate template.Template + endpointTemplate *template.Template measurementCode string } diff --git a/adapters/adocean/usersync.go b/adapters/adocean/usersync.go deleted file mode 100644 index b189f822b46..00000000000 --- a/adapters/adocean/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package adocean - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdOceanSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adocean", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/adocean/usersync_test.go b/adapters/adocean/usersync_test.go deleted file mode 100644 index 5257017adfa..00000000000 --- a/adapters/adocean/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package adocean - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestAdOceanSyncer(t *testing.T) { - syncURL := "https://sync-host.com/redataredir/?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&url=localhost%2Fsetuid%3Fbidder%3Dadocean%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3DUUID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdOceanSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "consent-string", - }, - }) - - assert.NoError(t, err) - assert.Equal( - t, - "https://sync-host.com/redataredir/?gdpr=1&gdpr_consent=consent-string&url=localhost%2Fsetuid%3Fbidder%3Dadocean%26gdpr%3D1%26gdpr_consent%3Dconsent-string%26uid%3DUUID", - syncInfo.URL, - ) - assert.Equal(t, "redirect", syncInfo.Type) -} diff --git a/adapters/adoppler/adoppler.go b/adapters/adoppler/adoppler.go index 015f31c7d01..20e88c45a52 100644 --- a/adapters/adoppler/adoppler.go +++ b/adapters/adoppler/adoppler.go @@ -200,7 +200,7 @@ func (ads *AdopplerAdapter) bidUri(ext *openrtb_ext.ExtImpAdoppler) (string, err params.AccountID = url.PathEscape(ext.Client) } - return macros.ResolveMacros(*ads.endpoint, params) + return macros.ResolveMacros(ads.endpoint, params) } func unmarshalExt(ext json.RawMessage) (*openrtb_ext.ExtImpAdoppler, error) { diff --git a/adapters/adpone/usersync.go b/adapters/adpone/usersync.go deleted file mode 100644 index 38dec19bb72..00000000000 --- a/adapters/adpone/usersync.go +++ /dev/null @@ -1,18 +0,0 @@ -package adpone - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -const adponeFamilyName = "adpone" - -func NewadponeSyncer(urlTemplate *template.Template) usersync.Usersyncer { - return adapters.NewSyncer( - adponeFamilyName, - urlTemplate, - adapters.SyncTypeRedirect, - ) -} diff --git a/adapters/adpone/usersync_test.go b/adapters/adpone/usersync_test.go deleted file mode 100644 index e744de5eb91..00000000000 --- a/adapters/adpone/usersync_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package adpone - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestAdponeSyncer(t *testing.T) { - syncURL := "https://usersync.adpone.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewadponeSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "https://usersync.adpone.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) -} diff --git a/adapters/adtarget/usersync.go b/adapters/adtarget/usersync.go deleted file mode 100644 index 088de8fb2ad..00000000000 --- a/adapters/adtarget/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package adtarget - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdtargetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adtarget", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/adtarget/usersync_test.go b/adapters/adtarget/usersync_test.go deleted file mode 100644 index e66b7b8377b..00000000000 --- a/adapters/adtarget/usersync_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package adtarget - -import ( - "fmt" - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy/ccpa" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestAdtargetSyncer(t *testing.T) { - syncURL := "//sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=localhost%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D" - fmt.Println("adtarget sync") - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdtargetSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - Consent: "123", - }, - CCPA: ccpa.Policy{ - Consent: "1-YY", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr=0&gdpr_consent=123&us_privacy=1-YY&redir=localhost%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D0%26gdpr_consent%3D123%26uid%3D%7Buid%7D", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/adtelligent/usersync.go b/adapters/adtelligent/usersync.go deleted file mode 100644 index 9198b30fe6f..00000000000 --- a/adapters/adtelligent/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package adtelligent - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdtelligentSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adtelligent", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/adtelligent/usersync_test.go b/adapters/adtelligent/usersync_test.go deleted file mode 100644 index 2ca0ddfc135..00000000000 --- a/adapters/adtelligent/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package adtelligent - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestAdtelligentSyncer(t *testing.T) { - syncURL := "//sync.adtelligent.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdtelligentSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//sync.adtelligent.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%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/adapters/advangelists/advangelists.go b/adapters/advangelists/advangelists.go index b5b385f6c06..d6fce56f487 100644 --- a/adapters/advangelists/advangelists.go +++ b/adapters/advangelists/advangelists.go @@ -15,7 +15,7 @@ import ( ) type AdvangelistsAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } //MakeRequests prepares request information for prebid-server core @@ -247,7 +247,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &AdvangelistsAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return bidder, nil } diff --git a/adapters/advangelists/usersync.go b/adapters/advangelists/usersync.go deleted file mode 100644 index 83930774773..00000000000 --- a/adapters/advangelists/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package advangelists - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdvangelistsSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("advangelists", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/advangelists/usersync_test.go b/adapters/advangelists/usersync_test.go deleted file mode 100644 index 04ee7968d87..00000000000 --- a/adapters/advangelists/usersync_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package advangelists - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestAdvangelistsSyncer(t *testing.T) { - syncURL := "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=advangelists&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&uid=$UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdvangelistsSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=advangelists&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&uid=$UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/adxcg/usersync.go b/adapters/adxcg/usersync.go deleted file mode 100644 index c6627059703..00000000000 --- a/adapters/adxcg/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package adxcg - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdxcgSyncer(urlTemplate *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adxcg", urlTemplate, adapters.SyncTypeRedirect) -} diff --git a/adapters/adxcg/usersync_test.go b/adapters/adxcg/usersync_test.go deleted file mode 100644 index 53a757f2732..00000000000 --- a/adapters/adxcg/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package adxcg - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestAdxcgSyncer(t *testing.T) { - syncURL := "https://app.adxcg.net/cma/cm-notify?pi=prebidsrvtst&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdxcgSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://app.adxcg.net/cma/cm-notify?pi=prebidsrvtst&gdpr=0&gdpr_consent=", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/adyoulike/usersync.go b/adapters/adyoulike/usersync.go deleted file mode 100644 index ffea6f69a27..00000000000 --- a/adapters/adyoulike/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package adyoulike - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAdyoulikeSyncer(urlTemplate *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adyoulike", urlTemplate, adapters.SyncTypeRedirect) -} diff --git a/adapters/adyoulike/usersync_test.go b/adapters/adyoulike/usersync_test.go deleted file mode 100644 index 72def4cf9b0..00000000000 --- a/adapters/adyoulike/usersync_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package adyoulike - -import ( - "testing" - "text/template" - - "github.com/stretchr/testify/assert" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" -) - -func TestAdyoulikeSyncer(t *testing.T) { - syncURL := "//visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr_consent_string={{.GDPRConsent}}&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAdyoulikeSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - }, - CCPA: ccpa.Policy{ - Consent: "1-YY", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr_consent_string=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&gdpr=1&us_privacy=1-YY", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/aja/usersync.go b/adapters/aja/usersync.go deleted file mode 100644 index 6a9fad74e32..00000000000 --- a/adapters/aja/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package aja - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAJASyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("aja", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/aja/usersync_test.go b/adapters/aja/usersync_test.go deleted file mode 100644 index dabd5e190b9..00000000000 --- a/adapters/aja/usersync_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package aja - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy/ccpa" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestAJASyncer(t *testing.T) { - syncURL := "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir=localhost/setuid?bidder=aja&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&uid=%s" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAJASyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", - }, - CCPA: ccpa.Policy{ - Consent: "C", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr=1&us_privacy=C&redir=localhost/setuid?bidder=aja&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&uid=%s", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/algorix/algorix.go b/adapters/algorix/algorix.go index 7c0a6787c7b..31d013490cf 100644 --- a/adapters/algorix/algorix.go +++ b/adapters/algorix/algorix.go @@ -16,7 +16,7 @@ import ( ) type adapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } // Builder builds a new instance of the AlgoriX adapter for the given bidder with the given config. @@ -26,7 +26,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) } bidder := &adapter{ - EndpointTemplate: *endpoint, + EndpointTemplate: endpoint, } return bidder, nil } diff --git a/adapters/amx/usersync.go b/adapters/amx/usersync.go deleted file mode 100644 index 17ad04d5cfb..00000000000 --- a/adapters/amx/usersync.go +++ /dev/null @@ -1,13 +0,0 @@ -package amx - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -// NewAMXSyncer produces an AMX RTB usersyncer -func NewAMXSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("amx", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/amx/usersync_test.go b/adapters/amx/usersync_test.go deleted file mode 100644 index b6b6e6babe8..00000000000 --- a/adapters/amx/usersync_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package amx - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestAMXSyncer(t *testing.T) { - syncURL := "http://pbs.amxrtb.com/cchain/0?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&cb=localhost%2Fsetuid%3Fbidder%3Damx%26uid%3D" - syncURLTemplate := template.Must(template.New("sync-template").Parse(syncURL)) - - syncer := NewAMXSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "http://pbs.amxrtb.com/cchain/0?gdpr=&gdpr_consent=&cb=localhost%2Fsetuid%3Fbidder%3Damx%26uid%3D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index e67df9539d8..cad52348134 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -608,7 +608,7 @@ func TestAppNexusLegacyBasicResponse(t *testing.T) { req.Header.Add("User-Agent", andata.deviceUA) req.Header.Add("X-Real-IP", andata.deviceIP) - pc := usersync.ParsePBSCookieFromRequest(req, &config.HostCookie{}) + pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) pc.TrySync("adnxs", andata.buyerUID) fakewriter := httptest.NewRecorder() diff --git a/adapters/appnexus/usersync.go b/adapters/appnexus/usersync.go deleted file mode 100644 index d29f0e3cb6b..00000000000 --- a/adapters/appnexus/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package appnexus - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAppnexusSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adnxs", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/appnexus/usersync_test.go b/adapters/appnexus/usersync_test.go deleted file mode 100644 index d01e5704e28..00000000000 --- a/adapters/appnexus/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package appnexus - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestAppNexusSyncer(t *testing.T) { - syncURL := "//ib.adnxs.com/getuid?https%3A%2F%2Fprebid.adnxs.com%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAppnexusSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "//ib.adnxs.com/getuid?https%3A%2F%2Fprebid.adnxs.com%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/audienceNetwork/usersync.go b/adapters/audienceNetwork/usersync.go deleted file mode 100644 index 4dd0b68ccff..00000000000 --- a/adapters/audienceNetwork/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package audienceNetwork - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewFacebookSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("audienceNetwork", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/audienceNetwork/usersync_test.go b/adapters/audienceNetwork/usersync_test.go deleted file mode 100644 index 591ea74f7ba..00000000000 --- a/adapters/audienceNetwork/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package audienceNetwork - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestFacebookSyncer(t *testing.T) { - syncURL := "https://www.facebook.com/audiencenetwork/idsync/?partner=partnerId&callback=localhost%2Fsetuid%3Fbidder%3DaudienceNetwork%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewFacebookSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "https://www.facebook.com/audiencenetwork/idsync/?partner=partnerId&callback=localhost%2Fsetuid%3Fbidder%3DaudienceNetwork%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/avocet/usersync.go b/adapters/avocet/usersync.go deleted file mode 100644 index 0cfa055ae86..00000000000 --- a/adapters/avocet/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package avocet - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewAvocetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("avocet", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/avocet/usersync_test.go b/adapters/avocet/usersync_test.go deleted file mode 100644 index bd4cd4145a2..00000000000 --- a/adapters/avocet/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package avocet - -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 TestAvocetSyncer(t *testing.T) { - syncURL := "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url=%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BUUID%7D%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewAvocetSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "ConsentString", - }, - CCPA: ccpa.Policy{ - Consent: "PrivacyString", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://ads.avct.cloud/getuid?&gdpr=1&gdpr_consent=ConsentString&us_privacy=PrivacyString&url=%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D1%26gdpr_consent%3DConsentString%26uid%3D%7B%7BUUID%7D%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/axonix/axonix.go b/adapters/axonix/axonix.go index 268b0e3ee2f..4a47360fe78 100644 --- a/adapters/axonix/axonix.go +++ b/adapters/axonix/axonix.go @@ -16,7 +16,7 @@ import ( ) type adapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { @@ -25,7 +25,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) } bidder := &adapter{ - EndpointTemplate: *endpoint, + EndpointTemplate: endpoint, } return bidder, nil } diff --git a/adapters/beachfront/usersync.go b/adapters/beachfront/usersync.go deleted file mode 100644 index 72466b59478..00000000000 --- a/adapters/beachfront/usersync.go +++ /dev/null @@ -1,15 +0,0 @@ -package beachfront - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewBeachfrontSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer( - "beachfront", - temp, - adapters.SyncTypeIframe) -} diff --git a/adapters/beachfront/usersync_test.go b/adapters/beachfront/usersync_test.go deleted file mode 100644 index edb52bd4f53..00000000000 --- a/adapters/beachfront/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package beachfront - -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 TestBeachfrontSyncer(t *testing.T) { - syncURL := "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewBeachfrontSyncer(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://sync.bfmio.com/sync_s2s?gdpr=A&us_privacy=C&url=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%5Bio_cid%5D", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/beintoo/usersync.go b/adapters/beintoo/usersync.go deleted file mode 100644 index fb60c6ab0a7..00000000000 --- a/adapters/beintoo/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package beintoo - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewBeintooSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("Beintoo", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/beintoo/usersync_test.go b/adapters/beintoo/usersync_test.go deleted file mode 100644 index 65d92e6d58f..00000000000 --- a/adapters/beintoo/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package beintoo - -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 TestBeintooSyncer(t *testing.T) { - syncURL := "https://ib.beintoo.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=localhost%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewBeintooSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://ib.beintoo.com/um?ssp=pbs&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&redirect=localhost%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/between/between.go b/adapters/between/between.go index f710855a4cd..f1cdd0ef5af 100644 --- a/adapters/between/between.go +++ b/adapters/between/between.go @@ -17,7 +17,7 @@ import ( ) type BetweenAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } func (a *BetweenAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -208,7 +208,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := BetweenAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return &bidder, nil } diff --git a/adapters/between/usersync.go b/adapters/between/usersync.go deleted file mode 100644 index 142282730a3..00000000000 --- a/adapters/between/usersync.go +++ /dev/null @@ -1,13 +0,0 @@ -package between - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -// NewBetweenSyncer returns "between" syncer -func NewBetweenSyncer(template *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("between", template, adapters.SyncTypeRedirect) -} diff --git a/adapters/between/usersync_test.go b/adapters/between/usersync_test.go deleted file mode 100644 index c54f473b209..00000000000 --- a/adapters/between/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package between - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestNewBetweenSyncerSyncer(t *testing.T) { - syncURL := "https://ads.betweendigital.com/match?bidder_id=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback_url=localhost:8080%2Fsetuid%3Fbidder%3Dbetween%26gdpr%3D0%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_ID%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewBetweenSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "https://ads.betweendigital.com/match?bidder_id=pbs&gdpr=&gdpr_consent=&us_privacy=&callback_url=localhost:8080%2Fsetuid%3Fbidder%3Dbetween%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24%7BUSER_ID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/bidmachine/bidmachine.go b/adapters/bidmachine/bidmachine.go index 14b72cc6c56..eae26c60145 100644 --- a/adapters/bidmachine/bidmachine.go +++ b/adapters/bidmachine/bidmachine.go @@ -18,7 +18,7 @@ import ( ) type adapter struct { - endpoint template.Template + endpoint *template.Template } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -171,7 +171,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &adapter{ - endpoint: *template, + endpoint: template, } return bidder, nil diff --git a/adapters/bidmyadz/usersync.go b/adapters/bidmyadz/usersync.go deleted file mode 100644 index 755a184d6e4..00000000000 --- a/adapters/bidmyadz/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package bidmyadz - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewBidmyadzSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("bidmyadz", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/bidmyadz/usersync_test.go b/adapters/bidmyadz/usersync_test.go deleted file mode 100644 index 11b5fedd73f..00000000000 --- a/adapters/bidmyadz/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package bidmyadz - -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 TestNewBidmyadzSyncer(t *testing.T) { - syncURL := "https://test.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - syncer := NewBidmyadzSyncer(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/c0f68227d14ed938c6c49f3967cbe9bc?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/bmtm/usersync.go b/adapters/bmtm/usersync.go deleted file mode 100644 index e89dec3b3a7..00000000000 --- a/adapters/bmtm/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package bmtm - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewBmtmSyncer(template *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("bmtm", template, adapters.SyncTypeRedirect) -} diff --git a/adapters/bmtm/usersync_test.go b/adapters/bmtm/usersync_test.go deleted file mode 100644 index e22ec822d63..00000000000 --- a/adapters/bmtm/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package bmtm - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestSyncer(t *testing.T) { - syncURL := "https://synctest/?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&url=localhost%2Fsetuid%3Fbidder%3Dbmtm%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3DUUID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewBmtmSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "consent-string", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://synctest/?gdpr=1&gdpr_consent=consent-string&url=localhost%2Fsetuid%3Fbidder%3Dbmtm%26gdpr%3D1%26gdpr_consent%3Dconsent-string%26uid%3DUUID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) -} diff --git a/adapters/brightroll/usersync.go b/adapters/brightroll/usersync.go deleted file mode 100644 index 099303fdd01..00000000000 --- a/adapters/brightroll/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package brightroll - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewBrightrollSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("brightroll", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/brightroll/usersync_test.go b/adapters/brightroll/usersync_test.go deleted file mode 100644 index fac17b0a9ae..00000000000 --- a/adapters/brightroll/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package brightroll - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestBrightrollSyncer(t *testing.T) { - syncURL := "http://test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url=localhost%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewBrightrollSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "http://test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr=&euconsent=&us_privacy=&url=localhost%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/colossus/usersync.go b/adapters/colossus/usersync.go deleted file mode 100644 index bb25b187352..00000000000 --- a/adapters/colossus/usersync.go +++ /dev/null @@ -1,13 +0,0 @@ -package colossus - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -// NewColossusSyncer returns colossus syncer -func NewColossusSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("colossus", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/colossus/usersync_test.go b/adapters/colossus/usersync_test.go deleted file mode 100644 index 4cb3a4bbbdf..00000000000 --- a/adapters/colossus/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package colossus - -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 TestColossusSyncer(t *testing.T) { - syncURL := "https://sync.colossusssp.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dcolossus%26uid%3D%5BUID%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewColossusSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - Consent: "A", - }, - CCPA: ccpa.Policy{ - Consent: "1-YY", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://sync.colossusssp.com/pbs.gif?gdpr=0&gdpr_consent=A&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dcolossus%26uid%3D%5BUID%5D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/connectad/usersync.go b/adapters/connectad/usersync.go deleted file mode 100644 index bae96656bce..00000000000 --- a/adapters/connectad/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package connectad - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewConnectAdSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("connectad", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/connectad/usersync_test.go b/adapters/connectad/usersync_test.go deleted file mode 100644 index 81b0dc47e33..00000000000 --- a/adapters/connectad/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package connectad - -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 TestConnectAdSyncer(t *testing.T) { - syncURL := "https://cdn.connectad.io/connectmyusers.php?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb=localhost%2Fsetuid%3Fbidder%3Dconnectad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewConnectAdSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "fakeconsent", - }, - CCPA: ccpa.Policy{ - Consent: "fake", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://cdn.connectad.io/connectmyusers.php?gdpr=1&consent=fakeconsent&us_privacy=fake&cb=localhost%2Fsetuid%3Fbidder%3Dconnectad%26gdpr%3D1%26gdpr_consent%3Dfakeconsent%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/consumable/usersync.go b/adapters/consumable/usersync.go deleted file mode 100644 index 0cb25ca9864..00000000000 --- a/adapters/consumable/usersync.go +++ /dev/null @@ -1,15 +0,0 @@ -package consumable - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewConsumableSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer( - "consumable", - temp, - adapters.SyncTypeRedirect) -} diff --git a/adapters/consumable/usersync_test.go b/adapters/consumable/usersync_test.go deleted file mode 100644 index 0af868a0a62..00000000000 --- a/adapters/consumable/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package consumable - -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 TestConsumableSyncer(t *testing.T) { - syncURL := "//e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewConsumableSyncer(syncURLTemplate) - u, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "A", - Consent: "B", - }, - CCPA: ccpa.Policy{ - Consent: "C", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//e.serverbid.com/udb/9969/match?gdpr=A&euconsent=B&us_privacy=C&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D", u.URL) - assert.Equal(t, "redirect", u.Type) - assert.Equal(t, false, u.SupportCORS) -} diff --git a/adapters/conversant/cnvr_legacy_test.go b/adapters/conversant/cnvr_legacy_test.go index e2000bc0dd1..fc34a93fae2 100644 --- a/adapters/conversant/cnvr_legacy_test.go +++ b/adapters/conversant/cnvr_legacy_test.go @@ -691,7 +691,7 @@ func ParseRequest(req *pbs.PBSRequest) (*pbs.PBSRequest, error) { // Need to pass the conversant user id thru uid cookie httpReq := httptest.NewRequest("POST", "/foo", body) - cookie := usersync.NewPBSCookie() + cookie := usersync.NewCookie() _ = cookie.TrySync("conversant", ExpectedBuyerUID) httpReq.Header.Set("Cookie", cookie.ToHTTPCookie(90*24*time.Hour).String()) httpReq.Header.Add("Referer", "http://example.com") diff --git a/adapters/conversant/usersync.go b/adapters/conversant/usersync.go deleted file mode 100644 index a38631f282c..00000000000 --- a/adapters/conversant/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package conversant - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewConversantSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("conversant", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/conversant/usersync_test.go b/adapters/conversant/usersync_test.go deleted file mode 100644 index 62ab732c443..00000000000 --- a/adapters/conversant/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package conversant - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestConversantSyncer(t *testing.T) { - syncURL := "usersync?rurl=localhost%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewConversantSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "usersync?rurl=localhost%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D0%26gdpr_consent%3D%26uid%3D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/cpmstar/usersync.go b/adapters/cpmstar/usersync.go deleted file mode 100644 index d3086f65b24..00000000000 --- a/adapters/cpmstar/usersync.go +++ /dev/null @@ -1,13 +0,0 @@ -package cpmstar - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -//NewCpmstarSyncer : -func NewCpmstarSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("cpmstar", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/cpmstar/usersync_test.go b/adapters/cpmstar/usersync_test.go deleted file mode 100644 index 9bae7062b1c..00000000000 --- a/adapters/cpmstar/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package cpmstar - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestCpmstarSyncer(t *testing.T) { - syncURL := "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26us_privacy%3D{{.USPrivacy}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewCpmstarSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "https://server.cpmstar.com/usersync.aspx?gdpr=&gdpr_consent=&us_privacy=&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D%26gdpr_consent%3D%26us_privacy%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.False(t, syncInfo.SupportCORS) -} diff --git a/adapters/criteo/usersync.go b/adapters/criteo/usersync.go deleted file mode 100644 index 3deba085ac4..00000000000 --- a/adapters/criteo/usersync.go +++ /dev/null @@ -1,14 +0,0 @@ -package criteo - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -// NewCriteoSyncer user syncer for Criteo -// Criteo doesn't need user synchronization yet. -func NewCriteoSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("criteo", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/criteo/usersync_test.go b/adapters/criteo/usersync_test.go deleted file mode 100644 index 9010d8bb450..00000000000 --- a/adapters/criteo/usersync_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package criteo - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestCriteoSyncer(t *testing.T) { - url := "//prebidserver/getuid?https%3A%2F%2Fprebidserver%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dcriteo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - temp := template.Must(template.New("sync-template").Parse(url)) - syncer := NewCriteoSyncer(temp) - info, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - }, - }) - assert.NoError(t, err) - assert.EqualValues(t, adapters.SyncTypeRedirect, info.Type) - assert.Equal(t, false, info.SupportCORS) - assert.Equal(t, "//prebidserver/getuid?https%3A%2F%2Fprebidserver%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dcriteo%26gdpr%3D1%26gdpr_consent%3D%26uid%3D%24UID", info.URL) -} diff --git a/adapters/datablocks/datablocks.go b/adapters/datablocks/datablocks.go index fbed456ace9..e9d727d70d4 100644 --- a/adapters/datablocks/datablocks.go +++ b/adapters/datablocks/datablocks.go @@ -16,7 +16,7 @@ import ( ) type DatablocksAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } func (a *DatablocksAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -186,7 +186,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &DatablocksAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return bidder, nil } diff --git a/adapters/datablocks/usersync.go b/adapters/datablocks/usersync.go deleted file mode 100644 index 2c69963ae74..00000000000 --- a/adapters/datablocks/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package datablocks - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewDatablocksSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("datablocks", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/datablocks/usersync_test.go b/adapters/datablocks/usersync_test.go deleted file mode 100644 index d6ee580408a..00000000000 --- a/adapters/datablocks/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package datablocks - -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 TestDatablocksSyncer(t *testing.T) { - syncURL := "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewDatablocksSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24%7Buid%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/deepintent/usersync.go b/adapters/deepintent/usersync.go deleted file mode 100644 index 7a17f5336d5..00000000000 --- a/adapters/deepintent/usersync.go +++ /dev/null @@ -1,13 +0,0 @@ -package deepintent - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -// NewDeepintentSyncer returns deepintent syncer -func NewDeepintentSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("deepintent", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/deepintent/usersync_test.go b/adapters/deepintent/usersync_test.go deleted file mode 100644 index 6709a838100..00000000000 --- a/adapters/deepintent/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package deepintent - -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 TestDeepintentSyncer(t *testing.T) { - syncURL := "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewDeepintentSyncer(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://match.deepintent.com/usersync/136?id=unk&gdpr=A&us_privacy=C&url=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%5Bio_cid%5D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/dmx/usersync.go b/adapters/dmx/usersync.go deleted file mode 100644 index 07fedf1c124..00000000000 --- a/adapters/dmx/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package dmx - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewDmxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("dmx", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/dmx/usersync_test.go b/adapters/dmx/usersync_test.go deleted file mode 100644 index a316af04cfb..00000000000 --- a/adapters/dmx/usersync_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package dmx - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - - "github.com/stretchr/testify/assert" -) - -func TestDmxSyncer(t *testing.T) { - temp := template.Must(template.New("sync-template").Parse("https://dmx.districtm.io/s/v1/img/s/10007")) - syncer := NewDmxSyncer(temp) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - assert.NoError(t, err) - assert.Equal(t, "https://dmx.districtm.io/s/v1/img/s/10007", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/e_volution/usersync.go b/adapters/e_volution/usersync.go deleted file mode 100644 index f22784d018b..00000000000 --- a/adapters/e_volution/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index d7a3eba5f0a..00000000000 --- a/adapters/e_volution/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -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/emx_digital/usersync.go b/adapters/emx_digital/usersync.go deleted file mode 100644 index 2d3011e0d69..00000000000 --- a/adapters/emx_digital/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package emx_digital - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewEMXDigitalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("emx_digital", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/emx_digital/usersync_test.go b/adapters/emx_digital/usersync_test.go deleted file mode 100644 index 7aaf133512f..00000000000 --- a/adapters/emx_digital/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package emx_digital - -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 TestEMXDigitalSyncer(t *testing.T) { - syncURL := "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=localhost%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewEMXDigitalSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://cs.emxdgt.com/um?ssp=pbs&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&redirect=localhost%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/engagebdr/usersync.go b/adapters/engagebdr/usersync.go deleted file mode 100644 index 205097b07be..00000000000 --- a/adapters/engagebdr/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package engagebdr - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewEngageBDRSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("engagebdr", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/engagebdr/usersync_test.go b/adapters/engagebdr/usersync_test.go deleted file mode 100644 index 5cee3abe35c..00000000000 --- a/adapters/engagebdr/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package engagebdr - -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 TestEngageBDRSyncer(t *testing.T) { - syncURL := "https://match.bnmla.com/usersync/s2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=localhost%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewEngageBDRSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://match.bnmla.com/usersync/s2s?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&r=localhost%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/eplanning/usersync.go b/adapters/eplanning/usersync.go deleted file mode 100644 index 6e8abf5de27..00000000000 --- a/adapters/eplanning/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package eplanning - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewEPlanningSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("eplanning", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/eplanning/usersync_test.go b/adapters/eplanning/usersync_test.go deleted file mode 100644 index 922ab4e20ce..00000000000 --- a/adapters/eplanning/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package eplanning - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestEPlanningSyncer(t *testing.T) { - syncURL := "https://ads.us.e-planning.net/uspd/1/?du=https%3A%2F%2Fads.us.e-planning.net%2Fgetuid%2F1%2F5a1ad71d2d53a0f5%3Flocalhost%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewEPlanningSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "https://ads.us.e-planning.net/uspd/1/?du=https%3A%2F%2Fads.us.e-planning.net%2Fgetuid%2F1%2F5a1ad71d2d53a0f5%3Flocalhost%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/gamma/usersync.go b/adapters/gamma/usersync.go deleted file mode 100644 index 4e4412a1efd..00000000000 --- a/adapters/gamma/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package gamma - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewGammaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("gamma", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/gamma/usersync_test.go b/adapters/gamma/usersync_test.go deleted file mode 100644 index 7636e05a6ef..00000000000 --- a/adapters/gamma/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package gamma - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestGammaSyncer(t *testing.T) { - syncURL := "//hb.gammaplatform.com/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dgamma%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewGammaSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//hb.gammaplatform.com/sync?gdpr=0&gdpr_consent=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dgamma%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.False(t, syncInfo.SupportCORS) -} diff --git a/adapters/gamoshi/usersync.go b/adapters/gamoshi/usersync.go deleted file mode 100644 index 9d6cdd0e518..00000000000 --- a/adapters/gamoshi/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package gamoshi - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewGamoshiSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("gamoshi", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/gamoshi/usersync_test.go b/adapters/gamoshi/usersync_test.go deleted file mode 100644 index 0465365f5e3..00000000000 --- a/adapters/gamoshi/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package gamoshi - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/stretchr/testify/assert" -) - -func TestGamoshiSyncer(t *testing.T) { - syncURL := "https://rtb.gamoshi.io/user_sync_prebid?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dgamoshi%26gdpr%3D%7B%7B.GDPR%7D%7D%26gdpr_consent%3D%7B%7B.GDPRConsent%7D%7D%26uid%3D%5Bgusr%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewGamoshiSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - CCPA: ccpa.Policy{ - Consent: "anyValue", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://rtb.gamoshi.io/user_sync_prebid?gdpr=&consent=&us_privacy=anyValue&rurl=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dgamoshi%26gdpr%3D%7B%7B.GDPR%7D%7D%26gdpr_consent%3D%7B%7B.GDPRConsent%7D%7D%26uid%3D%5Bgusr%5D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/grid/usersync.go b/adapters/grid/usersync.go deleted file mode 100644 index 31788ea3f06..00000000000 --- a/adapters/grid/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package grid - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewGridSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("grid", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/grid/usersync_test.go b/adapters/grid/usersync_test.go deleted file mode 100644 index 2d7f3fc6a4f..00000000000 --- a/adapters/grid/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package grid - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestGridSyncer(t *testing.T) { - syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewGridSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/gumgum/usersync.go b/adapters/gumgum/usersync.go deleted file mode 100644 index defef87dad9..00000000000 --- a/adapters/gumgum/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package gumgum - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewGumGumSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("gumgum", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/gumgum/usersync_test.go b/adapters/gumgum/usersync_test.go deleted file mode 100644 index 9b7c7aa4578..00000000000 --- a/adapters/gumgum/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package gumgum - -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 TestGumGumSyncer(t *testing.T) { - syncURL := "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=localhost%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewGumGumSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://rtb.gumgum.com/usync/prbds2s?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&r=localhost%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/improvedigital/usersync.go b/adapters/improvedigital/usersync.go deleted file mode 100644 index a58b97c3864..00000000000 --- a/adapters/improvedigital/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package improvedigital - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewImprovedigitalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("improvedigital", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/improvedigital/usersync_test.go b/adapters/improvedigital/usersync_test.go deleted file mode 100644 index 9892e0cc703..00000000000 --- a/adapters/improvedigital/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package improvedigital - -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 TestImprovedigitalSyncer(t *testing.T) { - syncURL := "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BPUB_USER_ID%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewImprovedigitalSyncer(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://ad.360yield.com/server_match?gdpr=A&gdpr_consent=B&us_privacy=C&r=%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%7BPUB_USER_ID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/inmobi/usersync.go b/adapters/inmobi/usersync.go deleted file mode 100644 index 7f022e3c5d0..00000000000 --- a/adapters/inmobi/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -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/invibes/invibes.go b/adapters/invibes/invibes.go index d29750c84bf..e0d4592c54a 100644 --- a/adapters/invibes/invibes.go +++ b/adapters/invibes/invibes.go @@ -70,7 +70,7 @@ func (a *InvibesInternalParams) IsTestRequest() bool { } type InvibesAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } // Builder builds a new instance of the Invibes adapter for the given bidder with the given config. @@ -81,7 +81,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := InvibesAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return &bidder, nil } diff --git a/adapters/invibes/usersync.go b/adapters/invibes/usersync.go deleted file mode 100644 index e6922444d44..00000000000 --- a/adapters/invibes/usersync.go +++ /dev/null @@ -1,16 +0,0 @@ -package invibes - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewInvibesSyncer(urlTemplate *template.Template) usersync.Usersyncer { - return adapters.NewSyncer( - "invibes", - urlTemplate, - adapters.SyncTypeIframe, - ) -} diff --git a/adapters/invibes/usersync_test.go b/adapters/invibes/usersync_test.go deleted file mode 100644 index 492886228fd..00000000000 --- a/adapters/invibes/usersync_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package invibes - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestInvibesSyncer(t *testing.T) { - syncURL := "http://localhost:56479/home/getLid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri=test.com%2Fsetuid%3Fbidder%3Dinvibes%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewInvibesSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "abc", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "http://localhost:56479/home/getLid?gdpr=1&gdpr_consent=abc&us_privacy=&redirectUri=test.com%2Fsetuid%3Fbidder%3Dinvibes%26gdpr%3D1%26gdpr_consent%3Dabc%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) - -} diff --git a/adapters/ix/usersync.go b/adapters/ix/usersync.go deleted file mode 100644 index 621fa17945a..00000000000 --- a/adapters/ix/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package ix - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewIxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("ix", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/ix/usersync_test.go b/adapters/ix/usersync_test.go deleted file mode 100644 index 3288b1ae443..00000000000 --- a/adapters/ix/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package ix - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestIxSyncer(t *testing.T) { - syncURL := "//ssum-sec.casalemedia.com/usermatchredir?s=184932&cb=localhost%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewIxSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "//ssum-sec.casalemedia.com/usermatchredir?s=184932&cb=localhost%2Fsetuid%3Fbidder%3Dix%26gdpr%3D%26gdpr_consent%3D%26uid%3D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/jixie/usersync.go b/adapters/jixie/usersync.go deleted file mode 100644 index 137d78f2859..00000000000 --- a/adapters/jixie/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package jixie - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewJixieSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("jixie", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/jixie/usersync_test.go b/adapters/jixie/usersync_test.go deleted file mode 100644 index 575482435ff..00000000000 --- a/adapters/jixie/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package jixie - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestJixieSyncer(t *testing.T) { - syncURL := "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewJixieSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "https://id.jixie.io/api/sync?pid=&gdpr=&gdpr_consent=&us_privacy=&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D%26gdpr_consent%3D%26uid%3D%25%25JXUID%25%25", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/kayzen/kayzen.go b/adapters/kayzen/kayzen.go index 1fcb9877e47..ea5408aaff3 100644 --- a/adapters/kayzen/kayzen.go +++ b/adapters/kayzen/kayzen.go @@ -15,7 +15,7 @@ import ( ) type adapter struct { - endpoint template.Template + endpoint *template.Template } // Builder builds a new instance of the Kayzen adapter for the given bidder with the given config. @@ -26,7 +26,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &adapter{ - endpoint: *template, + endpoint: template, } return bidder, nil } diff --git a/adapters/krushmedia/krushmedia.go b/adapters/krushmedia/krushmedia.go index f1b80da701f..488fd0fab8d 100644 --- a/adapters/krushmedia/krushmedia.go +++ b/adapters/krushmedia/krushmedia.go @@ -16,7 +16,7 @@ import ( ) type KrushmediaAdapter struct { - endpoint template.Template + endpoint *template.Template } // Builder builds a new instance of the KrushmediaA adapter for the given bidder with the given config. @@ -27,7 +27,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &KrushmediaAdapter{ - endpoint: *template, + endpoint: template, } return bidder, nil } diff --git a/adapters/krushmedia/usersync.go b/adapters/krushmedia/usersync.go deleted file mode 100644 index 860cb2204e9..00000000000 --- a/adapters/krushmedia/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package krushmedia - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewKrushmediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("krushmedia", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/krushmedia/usersync_test.go b/adapters/krushmedia/usersync_test.go deleted file mode 100644 index 765b0faa18b..00000000000 --- a/adapters/krushmedia/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package krushmedia - -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 TestKrushmediaSyncer(t *testing.T) { - syncURL := "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - syncer := NewKrushmediaSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - Consent: "allGdpr", - }, - CCPA: ccpa.Policy{ - Consent: "1-YY", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr=0&gdpr_consent=allGdpr&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/lockerdome/usersync.go b/adapters/lockerdome/usersync.go deleted file mode 100644 index d5cce16804a..00000000000 --- a/adapters/lockerdome/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package lockerdome - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewLockerDomeSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("lockerdome", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/lockerdome/usersync_test.go b/adapters/lockerdome/usersync_test.go deleted file mode 100644 index 3a2bd7f325b..00000000000 --- a/adapters/lockerdome/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package lockerdome - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestLockerDomeSyncer(t *testing.T) { - syncURL := "https://lockerdome.com/usync/prebidserver?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewLockerDomeSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "https://lockerdome.com/usync/prebidserver?pid=&gdpr=&gdpr_consent=&us_privacy=&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D%26gdpr_consent%3D%26uid%3D%7B%7Buid%7D%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/logicad/usersync.go b/adapters/logicad/usersync.go deleted file mode 100644 index e685cc985fc..00000000000 --- a/adapters/logicad/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package logicad - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewLogicadSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("logicad", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/logicad/usersync_test.go b/adapters/logicad/usersync_test.go deleted file mode 100644 index e8b10c665fe..00000000000 --- a/adapters/logicad/usersync_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package logicad - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestLogicadSyncer(t *testing.T) { - syncURL := "https://localhost/cookiesender?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru=localhost%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewLogicadSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "A", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://localhost/cookiesender?r=true&gdpr=1&gdpr_consent=A&ru=localhost%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/lunamedia/lunamedia.go b/adapters/lunamedia/lunamedia.go index 899269e661f..5c4852645e6 100644 --- a/adapters/lunamedia/lunamedia.go +++ b/adapters/lunamedia/lunamedia.go @@ -15,7 +15,7 @@ import ( ) type LunaMediaAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } //MakeRequests prepares request information for prebid-server core @@ -235,7 +235,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &LunaMediaAdapter{ - EndpointTemplate: *urlTemplate, + EndpointTemplate: urlTemplate, } return bidder, nil } diff --git a/adapters/lunamedia/usersync.go b/adapters/lunamedia/usersync.go deleted file mode 100644 index 39c9a808040..00000000000 --- a/adapters/lunamedia/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package lunamedia - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewLunaMediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("lunamedia", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/lunamedia/usersync_test.go b/adapters/lunamedia/usersync_test.go deleted file mode 100644 index 24cd740d600..00000000000 --- a/adapters/lunamedia/usersync_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package lunamedia - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestLunaMediaSyncer(t *testing.T) { - syncURL := "https://api.lunamedia.io/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=lunamedia&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&uid=$UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewLunaMediaSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "A", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://api.lunamedia.io/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=lunamedia&gdpr=1&gdpr_consent=A&uid=$UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/madvertise/madvertise.go b/adapters/madvertise/madvertise.go index dfc996d2497..6d37ccfcc80 100644 --- a/adapters/madvertise/madvertise.go +++ b/adapters/madvertise/madvertise.go @@ -15,7 +15,7 @@ import ( ) type adapter struct { - endpointTemplate template.Template + endpointTemplate *template.Template } func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { @@ -25,7 +25,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &adapter{ - endpointTemplate: *template, + endpointTemplate: template, } return bidder, nil diff --git a/adapters/marsmedia/usersync.go b/adapters/marsmedia/usersync.go deleted file mode 100644 index 4ac76d1f5f2..00000000000 --- a/adapters/marsmedia/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package marsmedia - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewMarsmediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("marsmedia", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/marsmedia/usersync_test.go b/adapters/marsmedia/usersync_test.go deleted file mode 100644 index 975af65fcf5..00000000000 --- a/adapters/marsmedia/usersync_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package marsmedia - -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 TestMarsmediaSyncer(t *testing.T) { - syncURL := "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=localhost:8000%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewMarsmediaSyncer(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://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr=A&gdpr_consent=B&us_privacy=C&redirect=localhost:8000%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUUID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) - -} diff --git a/adapters/mediafuse/usersync.go b/adapters/mediafuse/usersync.go deleted file mode 100644 index d482ad774bd..00000000000 --- a/adapters/mediafuse/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package mediafuse - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewMediafuseSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("mediafuse", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/mediafuse/usersync_test.go b/adapters/mediafuse/usersync_test.go deleted file mode 100644 index 95045689a87..00000000000 --- a/adapters/mediafuse/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package mediafuse - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestMediafuseSyncer(t *testing.T) { - syncURL := "//sync.hbmp.mediafuse.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 := NewMediafuseSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//sync.hbmp.mediafuse.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/adapters/mgid/usersync.go b/adapters/mgid/usersync.go deleted file mode 100644 index 94cf12e119d..00000000000 --- a/adapters/mgid/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package mgid - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewMgidSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("mgid", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/mgid/usersync_test.go b/adapters/mgid/usersync_test.go deleted file mode 100644 index 3fa5a151bd8..00000000000 --- a/adapters/mgid/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package mgid - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestMgidSyncer(t *testing.T) { - syncURL := "https://cm.mgid.com/m?cdsp=363893&adu=https%3A//external.com%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewMgidSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://cm.mgid.com/m?cdsp=363893&adu=https%3A//external.com%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Bmuidn%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go index 47ee1cab743..3437a0f6dba 100644 --- a/adapters/mobilefuse/mobilefuse.go +++ b/adapters/mobilefuse/mobilefuse.go @@ -16,7 +16,7 @@ import ( ) type MobileFuseAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } // Builder builds a new instance of the MobileFuse adapter for the given bidder with the given config. @@ -27,7 +27,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &MobileFuseAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return bidder, nil } diff --git a/adapters/nanointeractive/usersync.go b/adapters/nanointeractive/usersync.go deleted file mode 100644 index 6bd9cd1f036..00000000000 --- a/adapters/nanointeractive/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package nanointeractive - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewNanoInteractiveSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("nanointeractive", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/nanointeractive/usersync_test.go b/adapters/nanointeractive/usersync_test.go deleted file mode 100644 index 4d816ab7384..00000000000 --- a/adapters/nanointeractive/usersync_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package nanointeractive - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/prebid/prebid-server/privacy/gdpr" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestNewNanoInteractiveSyncer(t *testing.T) { - syncURL := "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - userSync := NewNanoInteractiveSyncer(syncURLTemplate) - syncInfo, err := userSync.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr=1&consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&redirectUri=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/ninthdecimal/ninthdecimal.go b/adapters/ninthdecimal/ninthdecimal.go index fc29b38cdab..17d55edb87a 100755 --- a/adapters/ninthdecimal/ninthdecimal.go +++ b/adapters/ninthdecimal/ninthdecimal.go @@ -15,7 +15,7 @@ import ( ) type NinthDecimalAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } //MakeRequests prepares request information for prebid-server core @@ -235,7 +235,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &NinthDecimalAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return bidder, nil } diff --git a/adapters/ninthdecimal/usersync.go b/adapters/ninthdecimal/usersync.go deleted file mode 100755 index a01fdb636e3..00000000000 --- a/adapters/ninthdecimal/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package ninthdecimal - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewNinthDecimalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("ninthdecimal", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/ninthdecimal/usersync_test.go b/adapters/ninthdecimal/usersync_test.go deleted file mode 100755 index e722a2b6e69..00000000000 --- a/adapters/ninthdecimal/usersync_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package ninthdecimal - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestNinthDecimalSyncer(t *testing.T) { - syncURL := "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=ninthdecimal&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&uid=$UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewNinthDecimalSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "A", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=ninthdecimal&gdpr=1&gdpr_consent=A&uid=$UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/nobid/usersync.go b/adapters/nobid/usersync.go deleted file mode 100644 index 442075648ce..00000000000 --- a/adapters/nobid/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package nobid - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewNoBidSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("nobid", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/nobid/usersync_test.go b/adapters/nobid/usersync_test.go deleted file mode 100644 index bc55d130509..00000000000 --- a/adapters/nobid/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package nobid - -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 TestNoBidSyncer(t *testing.T) { - syncURL := "https://ads.servenobid.com/sync?tek=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=localhost%2Fsetuid%3Fbidder%3Dtest_bidder%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewNoBidSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://ads.servenobid.com/sync?tek=pbs&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&redirect=localhost%2Fsetuid%3Fbidder%3Dtest_bidder%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/onetag/onetag.go b/adapters/onetag/onetag.go index 1721d76df6a..30ebf492cab 100644 --- a/adapters/onetag/onetag.go +++ b/adapters/onetag/onetag.go @@ -15,7 +15,7 @@ import ( ) type adapter struct { - endpointTemplate template.Template + endpointTemplate *template.Template } func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { @@ -25,7 +25,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &adapter{ - endpointTemplate: *template, + endpointTemplate: template, } return bidder, nil diff --git a/adapters/onetag/usersync.go b/adapters/onetag/usersync.go deleted file mode 100644 index 9a2b700dd3d..00000000000 --- a/adapters/onetag/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package onetag - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSyncer(template *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("onetag", template, adapters.SyncTypeIframe) -} diff --git a/adapters/onetag/usersync_test.go b/adapters/onetag/usersync_test.go deleted file mode 100644 index 21f4837d5e1..00000000000 --- a/adapters/onetag/usersync_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package onetag - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestOneTagSyncer(t *testing.T) { - syncURL := "https://onetag-sys.com/usync/?redir=" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "https://onetag-sys.com/usync/?redir=", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) -} diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go index b7d03fbfc6c..035f4d9b679 100644 --- a/adapters/openrtb_util_test.go +++ b/adapters/openrtb_util_test.go @@ -341,7 +341,7 @@ func TestOpenRTBEmptyUser(t *testing.T) { } func TestOpenRTBUserWithCookie(t *testing.T) { - pbsCookie := usersync.NewPBSCookie() + pbsCookie := usersync.NewCookie() pbsCookie.TrySync("test", "abcde") pbReq := pbs.PBSRequest{ User: &openrtb2.User{}, diff --git a/adapters/openx/usersync.go b/adapters/openx/usersync.go deleted file mode 100644 index 875b60fbd10..00000000000 --- a/adapters/openx/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package openx - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewOpenxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("openx", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/openx/usersync_test.go b/adapters/openx/usersync_test.go deleted file mode 100644 index 14ec38be118..00000000000 --- a/adapters/openx/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package openx - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestOpenxSyncer(t *testing.T) { - syncURL := "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r=localhost%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewOpenxSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "https://rtb.openx.net/sync/prebid?gdpr=&gdpr_consent=&r=localhost%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/operaads/operaads.go b/adapters/operaads/operaads.go index 890a15ddb5f..5c072354da2 100644 --- a/adapters/operaads/operaads.go +++ b/adapters/operaads/operaads.go @@ -3,15 +3,16 @@ 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/macros" "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type adapter struct { @@ -80,7 +81,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E } macro := macros.EndpointTemplateParams{PublisherID: operaadsExt.PublisherID, AccountID: operaadsExt.EndpointID} - endpoint, err := macros.ResolveMacros(*a.epTemplate, ¯o) + endpoint, err := macros.ResolveMacros(a.epTemplate, ¯o) if err != nil { errs = append(errs, err) continue diff --git a/adapters/operaads/usersync.go b/adapters/operaads/usersync.go deleted file mode 100644 index aae6fb600e3..00000000000 --- a/adapters/operaads/usersync.go +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index e9b402ac465..00000000000 --- a/adapters/operaads/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -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/usersync.go b/adapters/outbrain/usersync.go deleted file mode 100644 index 7dd60306c60..00000000000 --- a/adapters/outbrain/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package outbrain - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewOutbrainSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("outbrain", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/outbrain/usersync_test.go b/adapters/outbrain/usersync_test.go deleted file mode 100644 index f531834fc48..00000000000 --- a/adapters/outbrain/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package outbrain - -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 TestSyncer(t *testing.T) { - syncURL := "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb=host%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewOutbrainSyncer(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, "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr=A&gdpr_consent=B&us_privacy=C&cb=host%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) -} diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 032a324d18a..2e8a6804850 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -661,7 +661,7 @@ func TestPubmaticSampleRequest(t *testing.T) { httpReq := httptest.NewRequest("POST", server.URL, body) httpReq.Header.Add("Referer", "http://test.com/sports") - pc := usersync.ParsePBSCookieFromRequest(httpReq, &config.HostCookie{}) + pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) pc.TrySync("pubmatic", "12345") fakewriter := httptest.NewRecorder() diff --git a/adapters/pubmatic/usersync.go b/adapters/pubmatic/usersync.go deleted file mode 100644 index 822f13cea3d..00000000000 --- a/adapters/pubmatic/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package pubmatic - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewPubmaticSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("pubmatic", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/pubmatic/usersync_test.go b/adapters/pubmatic/usersync_test.go deleted file mode 100644 index 0f4d2e857d3..00000000000 --- a/adapters/pubmatic/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package pubmatic - -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 TestPubmaticSyncer(t *testing.T) { - syncURL := "//ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect=localhost%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewPubmaticSyncer(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, "//ads.pubmatic.com/AdServer/js/user_sync.html?gdpr=A&gdpr_consent=B&us_privacy=C&predirect=localhost%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index fac0bfd4de8..a4e20b04859 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -239,7 +239,7 @@ func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { // setup a http request httpReq := httptest.NewRequest("POST", CreateService(adapterstest.BidOnTags("")).Server.URL, body) httpReq.Header.Add("Referer", "http://news.pub/topnews") - pc := usersync.ParsePBSCookieFromRequest(httpReq, &config.HostCookie{}) + pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) pc.TrySync("pulsepoint", "pulsepointUser123") fakewriter := httptest.NewRecorder() diff --git a/adapters/pulsepoint/usersync.go b/adapters/pulsepoint/usersync.go deleted file mode 100644 index 1b6903f9f02..00000000000 --- a/adapters/pulsepoint/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package pulsepoint - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewPulsepointSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("pulsepoint", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/pulsepoint/usersync_test.go b/adapters/pulsepoint/usersync_test.go deleted file mode 100644 index 7cfea57cb91..00000000000 --- a/adapters/pulsepoint/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package pulsepoint - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestPulsepointSyncer(t *testing.T) { - syncURL := "//bh.contextweb.com/rtset?pid=561205&ev=1&rurl=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewPulsepointSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "//bh.contextweb.com/rtset?pid=561205&ev=1&rurl=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D%26gdpr_consent%3D%26uid%3D%25%25VGUID%25%25", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/rhythmone/usersync.go b/adapters/rhythmone/usersync.go deleted file mode 100644 index 990fcb065f6..00000000000 --- a/adapters/rhythmone/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package rhythmone - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewRhythmoneSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("rhythmone", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/rhythmone/usersync_test.go b/adapters/rhythmone/usersync_test.go deleted file mode 100644 index 97920fb4980..00000000000 --- a/adapters/rhythmone/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package rhythmone - -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 TestRhythmoneSyncer(t *testing.T) { - syncURL := "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=localhost%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewRhythmoneSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://sync.1rx.io/usersync2/rmphb?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&redir=localhost%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D%5BRX_UUID%5D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/rtbhouse/usersync.go b/adapters/rtbhouse/usersync.go deleted file mode 100644 index baa0b994373..00000000000 --- a/adapters/rtbhouse/usersync.go +++ /dev/null @@ -1,18 +0,0 @@ -package rtbhouse - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -const rtbHouseFamilyName = "rtbhouse" - -func NewRTBHouseSyncer(urlTemplate *template.Template) usersync.Usersyncer { - return adapters.NewSyncer( - rtbHouseFamilyName, - urlTemplate, - adapters.SyncTypeRedirect, - ) -} diff --git a/adapters/rtbhouse/usersync_test.go b/adapters/rtbhouse/usersync_test.go deleted file mode 100644 index a7cb2590f90..00000000000 --- a/adapters/rtbhouse/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package rtbhouse - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestRTBHouseSyncer(t *testing.T) { - syncURL := "https://creativecdn.com/cm-notify?pi=prebidsrvtst&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewRTBHouseSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://creativecdn.com/cm-notify?pi=prebidsrvtst&gdpr=0&gdpr_consent=", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index a77592e1bb3..2d4c962b53f 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -5,32 +5,28 @@ import ( "context" "encoding/json" "errors" - "github.com/buger/jsonparser" - "github.com/stretchr/testify/mock" + "fmt" "io/ioutil" "net/http" "net/http/httptest" "strconv" + "strings" "testing" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/errortypes" - + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "fmt" - - "strings" - - "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" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) type rubiAppendTrackerUrlTestScenario struct { @@ -1035,7 +1031,7 @@ func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdap req.Header.Add("User-Agent", rubidata.deviceUA) req.Header.Add("X-Real-IP", rubidata.deviceIP) - pc := usersync.ParsePBSCookieFromRequest(req, &config.HostCookie{}) + pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) pc.TrySync("rubicon", rubidata.buyerUID) fakewriter := httptest.NewRecorder() diff --git a/adapters/rubicon/usersync.go b/adapters/rubicon/usersync.go deleted file mode 100644 index a4ab464a73f..00000000000 --- a/adapters/rubicon/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package rubicon - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewRubiconSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("rubicon", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/rubicon/usersync_test.go b/adapters/rubicon/usersync_test.go deleted file mode 100644 index eca4056206e..00000000000 --- a/adapters/rubicon/usersync_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package rubicon - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestRubiconSyncer(t *testing.T) { - syncURL := "https://pixel.rubiconproject.com/exchange/sync.php?p=prebid&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewRubiconSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://pixel.rubiconproject.com/exchange/sync.php?p=prebid&gdpr=0&gdpr_consent=", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) - assert.Equal(t, "rubicon", syncer.FamilyName()) -} diff --git a/adapters/sa_lunamedia/usersync.go b/adapters/sa_lunamedia/usersync.go deleted file mode 100644 index f78b7944cb2..00000000000 --- a/adapters/sa_lunamedia/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index e3820fbc1af..00000000000 --- a/adapters/sa_lunamedia/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -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/usersync.go b/adapters/sharethrough/usersync.go deleted file mode 100644 index f76f41ca83e..00000000000 --- a/adapters/sharethrough/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package sharethrough - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSharethroughSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("sharethrough", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/sharethrough/usersync_test.go b/adapters/sharethrough/usersync_test.go deleted file mode 100644 index 00b3c427fb8..00000000000 --- a/adapters/sharethrough/usersync_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package sharethrough - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestSharethroughSyncer(t *testing.T) { - syncURL := "https://match.sharethrough.com?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewSharethroughSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://match.sharethrough.com?gdpr=0&gdpr_consent=", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) - assert.Equal(t, "sharethrough", syncer.FamilyName()) -} diff --git a/adapters/silvermob/silvermob.go b/adapters/silvermob/silvermob.go index 8a5c6b259b6..a2060d25fb5 100644 --- a/adapters/silvermob/silvermob.go +++ b/adapters/silvermob/silvermob.go @@ -15,7 +15,7 @@ import ( ) type SilverMobAdapter struct { - endpoint template.Template + endpoint *template.Template } // Builder builds a new instance of the SilverMob adapter for the given bidder with the given config. @@ -26,7 +26,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &SilverMobAdapter{ - endpoint: *template, + endpoint: template, } return bidder, nil } diff --git a/adapters/smartadserver/usersync.go b/adapters/smartadserver/usersync.go deleted file mode 100644 index f7199965441..00000000000 --- a/adapters/smartadserver/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package smartadserver - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSmartadserverSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smartadserver", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/smartadserver/usersync_test.go b/adapters/smartadserver/usersync_test.go deleted file mode 100644 index 319ce5b58a6..00000000000 --- a/adapters/smartadserver/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package smartadserver - -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 TestSmartadserverSyncer(t *testing.T) { - syncURL := "//ssbsync.smartadserver.com/getuid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url=localhost%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bsas_uid%5D%22" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewSmartadserverSyncer(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, "//ssbsync.smartadserver.com/getuid?gdpr=1&gdpr_consent=COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA&us_privacy=1YNN&url=localhost%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D1%26gdpr_consent%3DCOyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA%26uid%3D%5Bsas_uid%5D%22", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/smarthub/smarthub.go b/adapters/smarthub/smarthub.go index 4a374fa208c..ba381a42c26 100644 --- a/adapters/smarthub/smarthub.go +++ b/adapters/smarthub/smarthub.go @@ -19,7 +19,7 @@ const ( ) type adapter struct { - endpoint template.Template + endpoint *template.Template } type bidExt struct { @@ -33,7 +33,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &adapter{ - endpoint: *template, + endpoint: template, } return bidder, nil diff --git a/adapters/smarthub/usersync.go b/adapters/smarthub/usersync.go deleted file mode 100644 index 3cd4de2a335..00000000000 --- a/adapters/smarthub/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package smarthub - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSmartHubSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smarthub", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/smarthub/usersync_test.go b/adapters/smarthub/usersync_test.go deleted file mode 100644 index b834b21b210..00000000000 --- a/adapters/smarthub/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package smarthub - -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 TestNewSmartHubSyncer(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 := NewSmartHubSyncer(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/smartrtb/smartrtb.go b/adapters/smartrtb/smartrtb.go index e123d4eb6d2..9fe25ff05c5 100644 --- a/adapters/smartrtb/smartrtb.go +++ b/adapters/smartrtb/smartrtb.go @@ -16,7 +16,7 @@ import ( // Base adapter structure. type SmartRTBAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } // Bid request extension appended to downstream request. @@ -49,7 +49,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &SmartRTBAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return bidder, nil } diff --git a/adapters/smartrtb/usersync.go b/adapters/smartrtb/usersync.go deleted file mode 100644 index 74ef0e9960b..00000000000 --- a/adapters/smartrtb/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package smartrtb - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSmartRTBSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smartrtb", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/smartrtb/usersync_test.go b/adapters/smartrtb/usersync_test.go deleted file mode 100644 index 68a8452a316..00000000000 --- a/adapters/smartrtb/usersync_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package smartrtb - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestSmartRTBSyncer(t *testing.T) { - temp := template.Must(template.New("sync-template").Parse("http://market-east.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&url=localhost%2Fsetuid%3Fbidder%smartrtb%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D")) - syncer := NewSmartRTBSyncer(temp) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - assert.NoError(t, err) - assert.Equal(t, "http://market-east.smrtb.com/sync/all?nid=smartrtb&gdpr=&gdpr_consent=&url=localhost%2Fsetuid%3Fbidder%smartrtb%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/smartyads/smartyads.go b/adapters/smartyads/smartyads.go index b5a09223eb0..d25508b8566 100644 --- a/adapters/smartyads/smartyads.go +++ b/adapters/smartyads/smartyads.go @@ -16,7 +16,7 @@ import ( ) type SmartyAdsAdapter struct { - endpoint template.Template + endpoint *template.Template } // Builder builds a new instance of the SmartyAds adapter for the given bidder with the given config. @@ -27,7 +27,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &SmartyAdsAdapter{ - endpoint: *template, + endpoint: template, } return bidder, nil } diff --git a/adapters/smartyads/usersync.go b/adapters/smartyads/usersync.go deleted file mode 100644 index 9075aa9bcd7..00000000000 --- a/adapters/smartyads/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package smartyads - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSmartyAdsSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smartyads", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/smartyads/usersync_test.go b/adapters/smartyads/usersync_test.go deleted file mode 100644 index 4f94591c634..00000000000 --- a/adapters/smartyads/usersync_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package smartyads - -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 TestSmartyAdsSyncer(t *testing.T) { - syncURL := "https://as.ck-ie.com/prebid.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dsmartyads%26uid%3D%5BUID%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - syncer := NewSmartyAdsSyncer(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://as.ck-ie.com/prebid.gif?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dsmartyads%26uid%3D%5BUID%5D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/smilewanted/usersync.go b/adapters/smilewanted/usersync.go deleted file mode 100644 index 8f29cb845d8..00000000000 --- a/adapters/smilewanted/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 497e5061554..00000000000 --- a/adapters/smilewanted/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -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/somoaudience/usersync.go b/adapters/somoaudience/usersync.go deleted file mode 100644 index 5d1ddd71bc6..00000000000 --- a/adapters/somoaudience/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package somoaudience - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSomoaudienceSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("somoaudience", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/somoaudience/usersync_test.go b/adapters/somoaudience/usersync_test.go deleted file mode 100644 index 2367a5674dd..00000000000 --- a/adapters/somoaudience/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package somoaudience - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestSomoaudienceSyncer(t *testing.T) { - syncURL := "//publisher-east.mobileadtrading.com/usersync?ru=localhost%2Fsetuid%3Fbidder%3Dsomoaudience%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewSomoaudienceSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "//publisher-east.mobileadtrading.com/usersync?ru=localhost%2Fsetuid%3Fbidder%3Dsomoaudience%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/sonobi/usersync.go b/adapters/sonobi/usersync.go deleted file mode 100644 index 6fedd8bfa05..00000000000 --- a/adapters/sonobi/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package sonobi - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSonobiSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("sonobi", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/sonobi/usersync_test.go b/adapters/sonobi/usersync_test.go deleted file mode 100644 index 995c3757ba4..00000000000 --- a/adapters/sonobi/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package sonobi - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestSonobiSyncer(t *testing.T) { - syncURL := "//sync.go.sonobi.com/us.gif?loc=external.com%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewSonobiSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//sync.go.sonobi.com/us.gif?loc=external.com%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D0%26gdpr%3D%26uid%3D%5BUID%5D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index c3290c30a2b..407c505437a 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -192,7 +192,7 @@ func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { httpReq.Header.Add("Referer", testUrl) httpReq.Header.Add("User-Agent", testUserAgent) httpReq.Header.Add("X-Forwarded-For", testIp) - pc := usersync.ParsePBSCookieFromRequest(httpReq, &config.HostCookie{}) + pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) pc.TrySync("sovrn", testSovrnUserId) fakewriter := httptest.NewRecorder() diff --git a/adapters/sovrn/usersync.go b/adapters/sovrn/usersync.go deleted file mode 100644 index 225f2888196..00000000000 --- a/adapters/sovrn/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package sovrn - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSovrnSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("sovrn", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/sovrn/usersync_test.go b/adapters/sovrn/usersync_test.go deleted file mode 100644 index 6c35ecdb05d..00000000000 --- a/adapters/sovrn/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package sovrn - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestSovrnSyncer(t *testing.T) { - syncURL := "//ap.lijit.com/pixel?redir=external.com%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewSovrnSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//ap.lijit.com/pixel?redir=external.com%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/synacormedia/synacormedia.go b/adapters/synacormedia/synacormedia.go index aec3169fe54..c6a19bd0005 100644 --- a/adapters/synacormedia/synacormedia.go +++ b/adapters/synacormedia/synacormedia.go @@ -15,7 +15,7 @@ import ( ) type SynacorMediaAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } type SyncEndpointTemplateParams struct { @@ -35,7 +35,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &SynacorMediaAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return bidder, nil } diff --git a/adapters/synacormedia/usersync.go b/adapters/synacormedia/usersync.go deleted file mode 100644 index c7fa5c42aea..00000000000 --- a/adapters/synacormedia/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package synacormedia - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewSynacorMediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("synacormedia", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/synacormedia/usersync_test.go b/adapters/synacormedia/usersync_test.go deleted file mode 100644 index ce45353c7e5..00000000000 --- a/adapters/synacormedia/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package synacormedia - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestSynacormediaSyncer(t *testing.T) { - syncURL := "//sync.technoratimedia.com/services?srv=cs&pid=70&cb=localhost%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewSynacorMediaSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "//sync.technoratimedia.com/services?srv=cs&pid=70&cb=localhost%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.False(t, syncInfo.SupportCORS) -} diff --git a/adapters/syncer.go b/adapters/syncer.go deleted file mode 100644 index b9752b50ce1..00000000000 --- a/adapters/syncer.go +++ /dev/null @@ -1,51 +0,0 @@ -package adapters - -import ( - "text/template" - - "github.com/prebid/prebid-server/macros" - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/usersync" -) - -type Syncer struct { - familyName string - syncType SyncType - urlTemplate *template.Template -} - -func NewSyncer(familyName string, urlTemplate *template.Template, syncType SyncType) *Syncer { - return &Syncer{ - familyName: familyName, - urlTemplate: urlTemplate, - syncType: syncType, - } -} - -type SyncType string - -const ( - SyncTypeRedirect SyncType = "redirect" - SyncTypeIframe SyncType = "iframe" -) - -func (s *Syncer) GetUsersyncInfo(privacyPolicies privacy.Policies) (*usersync.UsersyncInfo, error) { - syncURL, err := macros.ResolveMacros(*s.urlTemplate, macros.UserSyncTemplateParams{ - GDPR: privacyPolicies.GDPR.Signal, - GDPRConsent: privacyPolicies.GDPR.Consent, - USPrivacy: privacyPolicies.CCPA.Consent, - }) - if err != nil { - return nil, err - } - - return &usersync.UsersyncInfo{ - URL: syncURL, - Type: string(s.syncType), - SupportCORS: false, - }, err -} - -func (s *Syncer) FamilyName() string { - return s.familyName -} diff --git a/adapters/syncer_test.go b/adapters/syncer_test.go deleted file mode 100644 index ca33a9a130d..00000000000 --- a/adapters/syncer_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package adapters - -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 TestGetUsersyncInfo(t *testing.T) { - privacyPolicies := privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "A", - Consent: "B", - }, - CCPA: ccpa.Policy{ - Consent: "C", - }, - } - - syncURL := "{{.GDPR}}{{.GDPRConsent}}{{.USPrivacy}}" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - syncer := Syncer{ - urlTemplate: syncURLTemplate, - } - - syncInfo, err := syncer.GetUsersyncInfo(privacyPolicies) - - assert.NoError(t, err) - assert.Equal(t, "ABC", syncInfo.URL) -} diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index 5f0710cf08a..4e519c2b124 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -22,7 +22,7 @@ const TAPPX_BIDDER_VERSION = "1.3" const TYPE_CNN = "prebid" type TappxAdapter struct { - endpointTemplate template.Template + endpointTemplate *template.Template } type Bidder struct { @@ -44,7 +44,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &TappxAdapter{ - endpointTemplate: *template, + endpointTemplate: template, } return bidder, nil } diff --git a/adapters/tappx/usersync.go b/adapters/tappx/usersync.go deleted file mode 100644 index 01477c35363..00000000000 --- a/adapters/tappx/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package tappx - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewTappxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("tappx", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/tappx/usersync_test.go b/adapters/tappx/usersync_test.go deleted file mode 100644 index 992a35de9a8..00000000000 --- a/adapters/tappx/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package tappx - -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 TestTappxSyncer(t *testing.T) { - syncURL := "//testing.ssp.tappx.com/cs/usersync.php?gdpr_optin={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&type=iframe&ruid=localhost%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BTPPXUID%7D%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewTappxSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "A", - }, - CCPA: ccpa.Policy{ - Consent: "1YNN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//testing.ssp.tappx.com/cs/usersync.php?gdpr_optin=1&gdpr_consent=A&us_privacy=1YNN&type=iframe&ruid=localhost%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%7B%7BTPPXUID%7D%7D", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/telaria/usersync.go b/adapters/telaria/usersync.go deleted file mode 100644 index 76be62026f8..00000000000 --- a/adapters/telaria/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package telaria - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewTelariaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("telaria", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/telaria/usersync_test.go b/adapters/telaria/usersync_test.go deleted file mode 100644 index f019ed5ceaf..00000000000 --- a/adapters/telaria/usersync_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package telaria - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestTelariaSyncer(t *testing.T) { - - syncURL := "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewTelariaSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://pbs.publishers.tremorhub.com/pubsync?gdpr=0&gdpr_consent=", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) - assert.Equal(t, "telaria", syncer.FamilyName()) - -} diff --git a/adapters/triplelift/usersync.go b/adapters/triplelift/usersync.go deleted file mode 100644 index 4a47615bd29..00000000000 --- a/adapters/triplelift/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package triplelift - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewTripleliftSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("triplelift", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/triplelift/usersync_test.go b/adapters/triplelift/usersync_test.go deleted file mode 100644 index 012e33a4d0a..00000000000 --- a/adapters/triplelift/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package triplelift - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestTripleliftSyncer(t *testing.T) { - syncURL := "//eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=https%3A%2F%2Feb2.3lift.com%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewTripleliftSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "//eb2.3lift.com/getuid?gdpr=&cmp_cs=&us_privacy=&redir=https%3A%2F%2Feb2.3lift.com%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/triplelift_native/usersync.go b/adapters/triplelift_native/usersync.go deleted file mode 100644 index 2a07740a761..00000000000 --- a/adapters/triplelift_native/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package triplelift_native - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewTripleliftSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("triplelift", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/triplelift_native/usersync_test.go b/adapters/triplelift_native/usersync_test.go deleted file mode 100644 index 7fd878b7f92..00000000000 --- a/adapters/triplelift_native/usersync_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package triplelift_native - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestTripleliftSyncer(t *testing.T) { - syncURL := "//eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=https%3A%2F%2Feb2.3lift.com%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewTripleliftSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "//eb2.3lift.com/getuid?gdpr=&cmp_cs=&us_privacy=&redir=https%3A%2F%2Feb2.3lift.com%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/trustx/usersync.go b/adapters/trustx/usersync.go deleted file mode 100644 index a738e8e9b79..00000000000 --- a/adapters/trustx/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package trustx - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewTrustXSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("trustx", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/trustx/usersync_test.go b/adapters/trustx/usersync_test.go deleted file mode 100644 index ba371bc2061..00000000000 --- a/adapters/trustx/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package trustx - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestTrustXSyncer(t *testing.T) { - syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewTrustXSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/ucfunnel/usersync.go b/adapters/ucfunnel/usersync.go deleted file mode 100644 index 0ae9948dd77..00000000000 --- a/adapters/ucfunnel/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package ucfunnel - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewUcfunnelSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("ucfunnel", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/ucfunnel/usersync_test.go b/adapters/ucfunnel/usersync_test.go deleted file mode 100644 index 204de978150..00000000000 --- a/adapters/ucfunnel/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package ucfunnel - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestUcfunnelSyncer(t *testing.T) { - syncURL := "//sync.aralego.com/idsync?gdpr={{.GDPR}}&redirect=externalURL.com%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewUcfunnelSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//sync.aralego.com/idsync?gdpr=0&redirect=externalURL.com%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/unruly/usersync.go b/adapters/unruly/usersync.go deleted file mode 100644 index 5dc60c859bb..00000000000 --- a/adapters/unruly/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package unruly - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewUnrulySyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("unruly", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/unruly/usersync_test.go b/adapters/unruly/usersync_test.go deleted file mode 100644 index f0702cebd34..00000000000 --- a/adapters/unruly/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package unruly - -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 TestUnrulySyncer(t *testing.T) { - syncURL := "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl=%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewUnrulySyncer(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://usermatch.targeting.unrulymedia.com/pbsync?gdpr=A&consent=B&us_privacy=C&rurl=%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/valueimpression/usersync.go b/adapters/valueimpression/usersync.go deleted file mode 100644 index 08490a5ed3e..00000000000 --- a/adapters/valueimpression/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package valueimpression - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewValueImpressionSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("valueimpression", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/valueimpression/usersync_test.go b/adapters/valueimpression/usersync_test.go deleted file mode 100644 index 7b3a13c5dd6..00000000000 --- a/adapters/valueimpression/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package valueimpression - -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 TestValueImpressionSyncer(t *testing.T) { - syncURL := "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewValueImpressionSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://rtb.valueimpression.com/usersync?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.False(t, syncInfo.SupportCORS) -} diff --git a/adapters/verizonmedia/usersync.go b/adapters/verizonmedia/usersync.go deleted file mode 100644 index 2c7354d1146..00000000000 --- a/adapters/verizonmedia/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package verizonmedia - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewVerizonMediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("verizonmedia", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/verizonmedia/usersync_test.go b/adapters/verizonmedia/usersync_test.go deleted file mode 100644 index 9dd1ae70c25..00000000000 --- a/adapters/verizonmedia/usersync_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package verizonmedia - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/stretchr/testify/assert" -) - -func TestVerizonMediaSyncer(t *testing.T) { - syncURL := "https://pixel.advertising.com/ups/58207/occ?http://localhost/%2Fsetuid%3Fbidder%3Dverizonmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewVerizonMediaSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) - - assert.NoError(t, err) - assert.Equal(t, "redirect", syncInfo.Type) -} diff --git a/adapters/viewdeos/usersync.go b/adapters/viewdeos/usersync.go deleted file mode 100644 index 05cb80c54a1..00000000000 --- a/adapters/viewdeos/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 8b8908a44e6..00000000000 --- a/adapters/viewdeos/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -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/adapters/visx/usersync.go b/adapters/visx/usersync.go deleted file mode 100644 index dc143570ab9..00000000000 --- a/adapters/visx/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package visx - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewVisxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("visx", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/visx/usersync_test.go b/adapters/visx/usersync_test.go deleted file mode 100644 index ec4cf82b04b..00000000000 --- a/adapters/visx/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package visx - -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 TestVisxSyncer(t *testing.T) { - syncURL := "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewVisxSyncer(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.visx.net/s2s_sync?gdpr=A&gdpr_consent=B&us_privacy=C&redir=%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUUID%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/vrtcal/usersync.go b/adapters/vrtcal/usersync.go deleted file mode 100644 index 8a6ddc0ca26..00000000000 --- a/adapters/vrtcal/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package vrtcal - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewVrtcalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("vrtcal", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/vrtcal/usersync_test.go b/adapters/vrtcal/usersync_test.go deleted file mode 100644 index 7f8ca220a96..00000000000 --- a/adapters/vrtcal/usersync_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package vrtcal - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestVrtcalSyncer(t *testing.T) { - syncURL := "http://usync-prebid.vrtcal.com/s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewVrtcalSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "http://usync-prebid.vrtcal.com/s?gdpr=0&gdpr_consent=", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) - assert.Equal(t, "vrtcal", syncer.FamilyName()) -} diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go index 8a692d4ff2e..51c9a44d770 100644 --- a/adapters/yeahmobi/yeahmobi.go +++ b/adapters/yeahmobi/yeahmobi.go @@ -17,7 +17,7 @@ import ( ) type YeahmobiAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } // Builder builds a new instance of the Yeahmobi adapter for the given bidder with the given config. @@ -28,7 +28,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &YeahmobiAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return bidder, nil } diff --git a/adapters/yieldlab/usersync.go b/adapters/yieldlab/usersync.go deleted file mode 100644 index 90507e31161..00000000000 --- a/adapters/yieldlab/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package yieldlab - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewYieldlabSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldlab", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/yieldlab/usersync_test.go b/adapters/yieldlab/usersync_test.go deleted file mode 100644 index eabd46f6dce..00000000000 --- a/adapters/yieldlab/usersync_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package yieldlab - -import ( - "testing" - "text/template" - - "github.com/stretchr/testify/assert" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" -) - -func TestYieldlabSyncer(t *testing.T) { - temp := template.Must(template.New("sync-template").Parse("https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25")) - syncer := NewYieldlabSyncer(temp) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - assert.NoError(t, err) - assert.Equal(t, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr=0&gdpr_consent=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%25%25YL_UID%25%25", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.False(t, syncInfo.SupportCORS) -} diff --git a/adapters/yieldmo/usersync.go b/adapters/yieldmo/usersync.go deleted file mode 100644 index d06caa90c0b..00000000000 --- a/adapters/yieldmo/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package yieldmo - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewYieldmoSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldmo", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/yieldmo/usersync_test.go b/adapters/yieldmo/usersync_test.go deleted file mode 100644 index 5d12c63e4aa..00000000000 --- a/adapters/yieldmo/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package yieldmo - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestYieldmoSyncer(t *testing.T) { - syncURL := "//ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewYieldmoSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//ads.yieldmo.com/pbsync?gdpr=0&gdpr_consent=&us_privacy=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.False(t, syncInfo.SupportCORS) -} diff --git a/adapters/yieldone/usersync.go b/adapters/yieldone/usersync.go deleted file mode 100644 index 4d5d8283a68..00000000000 --- a/adapters/yieldone/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package yieldone - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewYieldoneSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldone", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/yieldone/usersync_test.go b/adapters/yieldone/usersync_test.go deleted file mode 100644 index c4d0fee92dd..00000000000 --- a/adapters/yieldone/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package yieldone - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestYieldoneSyncer(t *testing.T) { - syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewYieldoneSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/zeroclickfraud/usersync.go b/adapters/zeroclickfraud/usersync.go deleted file mode 100644 index 41c589818ca..00000000000 --- a/adapters/zeroclickfraud/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package zeroclickfraud - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewZeroClickFraudSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("zeroclickfraud", temp, adapters.SyncTypeIframe) -} diff --git a/adapters/zeroclickfraud/usersync_test.go b/adapters/zeroclickfraud/usersync_test.go deleted file mode 100644 index 5e8f8fdf111..00000000000 --- a/adapters/zeroclickfraud/usersync_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package zeroclickfraud - -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 TestZeroClickFraudSyncer(t *testing.T) { - syncURL := "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewZeroClickFraudSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "1", - Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - }, - CCPA: ccpa.Policy{ - Consent: "1NYN", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "https://s.0cf.io/sync?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24%7Buid%7D", syncInfo.URL) - assert.Equal(t, "iframe", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go index cf27c83d4a7..862fd173205 100644 --- a/adapters/zeroclickfraud/zeroclickfraud.go +++ b/adapters/zeroclickfraud/zeroclickfraud.go @@ -16,7 +16,7 @@ import ( ) type ZeroClickFraudAdapter struct { - EndpointTemplate template.Template + EndpointTemplate *template.Template } func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -185,7 +185,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } bidder := &ZeroClickFraudAdapter{ - EndpointTemplate: *template, + EndpointTemplate: template, } return bidder, nil } diff --git a/analytics/core.go b/analytics/core.go index 38c4f779327..c15ae201ac0 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -6,7 +6,6 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/usersync" ) /* @@ -71,7 +70,19 @@ type SetUIDObject struct { type CookieSyncObject struct { Status int Errors []error - BidderStatus []*usersync.CookieSyncBidders + BidderStatus []*CookieSyncBidder +} + +type CookieSyncBidder struct { + BidderCode string `json:"bidder"` + NoCookie bool `json:"no_cookie,omitempty"` + UsersyncInfo *UsersyncInfo `json:"usersync,omitempty"` +} + +type UsersyncInfo struct { + URL string `json:"url,omitempty"` + Type string `json:"type,omitempty"` + SupportCORS bool `json:"supportCORS,omitempty"` } // NotificationEvent is a loggable object diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index 0c3d3c9e6ac..74d7c7d50f5 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -6,11 +6,10 @@ import ( "strings" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const TEST_DIR string = "testFiles" @@ -59,7 +58,7 @@ func TestSetUIDObject_ToJson(t *testing.T) { func TestCookieSyncObject_ToJson(t *testing.T) { cso := &analytics.CookieSyncObject{ Status: http.StatusOK, - BidderStatus: []*usersync.CookieSyncBidders{}, + BidderStatus: []*analytics.CookieSyncBidder{}, } if csoJson := jsonifyCookieSync(cso); strings.Contains(csoJson, "Transactional Logs Error") { t.Fatalf("CookieSyncObject failed to convert to json") diff --git a/analytics/pubstack/helpers/json_test.go b/analytics/pubstack/helpers/json_test.go index 7673b067f8a..9c33407db3a 100644 --- a/analytics/pubstack/helpers/json_test.go +++ b/analytics/pubstack/helpers/json_test.go @@ -6,7 +6,6 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/usersync" ) func TestJsonifyAuctionObject(t *testing.T) { @@ -31,7 +30,7 @@ func TestJsonifyVideoObject(t *testing.T) { func TestJsonifyCookieSync(t *testing.T) { cso := &analytics.CookieSyncObject{ Status: http.StatusOK, - BidderStatus: []*usersync.CookieSyncBidders{}, + BidderStatus: []*analytics.CookieSyncBidder{}, } if _, err := JsonifyCookieSync(cso, "scopeId"); err != nil { t.Fail() diff --git a/config/adapter.go b/config/adapter.go index 8cdad538dbf..eae47981a70 100644 --- a/config/adapter.go +++ b/config/adapter.go @@ -9,25 +9,13 @@ import ( ) type Adapter struct { - Endpoint string `mapstructure:"endpoint"` // Required - // UserSyncURL is the URL returned by /cookie_sync for this Bidder. It is _usually_ optional. - // If not defined, sensible defaults will be derived based on the config.external_url. - // Note that some Bidders don't have sensible defaults, because their APIs require an ID that will vary - // from one PBS host to another. - // - // For these bidders, there will be a warning logged on startup that usersyncs will not work if you have not - // defined one in the app config. Check your app logs for more info. - // - // This value will be interpreted as a Golang Template. At runtime, the following Template variables will be replaced. - // - // {{.GDPR}} -- This will be replaced with the "gdpr" property sent to /cookie_sync - // {{.Consent}} -- This will be replaced with the "consent" property sent to /cookie_sync - // {{.USPrivacy}} -- This will be replaced with the "us_privacy" property sent to /cookie_sync - // - // For more info on templates, see: https://golang.org/pkg/text/template/ - UserSyncURL string `mapstructure:"usersync_url"` - Disabled bool `mapstructure:"disabled"` - ExtraAdapterInfo string `mapstructure:"extra_info"` + Disabled bool `mapstructure:"disabled"` + Endpoint string `mapstructure:"endpoint"` + ExtraAdapterInfo string `mapstructure:"extra_info"` + Syncer *Syncer `mapstructure:"usersync"` + + // needed for backwards compatibility + UserSyncURL string `mapstructure:"usersync_url"` // needed for Rubicon XAPI AdapterXAPI `mapstructure:"xapi"` @@ -47,25 +35,20 @@ type AdapterXAPI struct { func validateAdapters(adapterMap map[string]Adapter, errs []error) []error { for adapterName, adapter := range adapterMap { if !adapter.Disabled { - // Verify that every adapter has a valid endpoint associated with it errs = validateAdapterEndpoint(adapter.Endpoint, adapterName, errs) - - // Verify that valid user_sync URLs are specified in the config - errs = validateAdapterUserSyncURL(adapter.UserSyncURL, adapterName, errs) } } return errs } -const ( - dummyHost string = "dummyhost.com" - dummyPublisherID string = "12" - dummyAccountID string = "some_account" - dummyGDPR string = "0" - dummyGDPRConsent string = "someGDPRConsentString" - dummyCCPA string = "1NYN" - dummyZoneID string = "zone" -) +var testEndpointTemplateParams = macros.EndpointTemplateParams{ + Host: "anyHost", + PublisherID: "anyPublisherID", + AccountID: "anyAccountID", + ZoneID: "anyZoneID", + SourceId: "anySourceID", + AdUnit: "anyAdUnit", +} // validateAdapterEndpoint makes sure that an adapter has a valid endpoint // associated with it @@ -81,12 +64,7 @@ func validateAdapterEndpoint(endpoint string, adapterName string, errs []error) return append(errs, fmt.Errorf("Invalid endpoint template: %s for adapter: %s. %v", endpoint, adapterName, err)) } // Resolve macros (if any) in the endpoint URL - resolvedEndpoint, err := macros.ResolveMacros(*endpointTemplate, macros.EndpointTemplateParams{ - Host: dummyHost, - PublisherID: dummyPublisherID, - AccountID: dummyAccountID, - ZoneID: dummyZoneID, - }) + resolvedEndpoint, err := macros.ResolveMacros(endpointTemplate, testEndpointTemplateParams) if err != nil { return append(errs, fmt.Errorf("Unable to resolve endpoint: %s for adapter: %s. %v", endpoint, adapterName, err)) } @@ -103,36 +81,3 @@ func validateAdapterEndpoint(endpoint string, adapterName string, errs []error) } return errs } - -// validateAdapterUserSyncURL validates an adapter's user sync URL if it is set -func validateAdapterUserSyncURL(userSyncURL string, adapterName string, errs []error) []error { - if userSyncURL != "" { - // Create user_sync URL template - userSyncTemplate, err := template.New("userSyncTemplate").Parse(userSyncURL) - if err != nil { - return append(errs, fmt.Errorf("Invalid user sync URL template: %s for adapter: %s. %v", userSyncURL, adapterName, err)) - } - // Resolve macros (if any) in the user_sync URL - dummyMacroValues := macros.UserSyncTemplateParams{ - GDPR: dummyGDPR, - GDPRConsent: dummyGDPRConsent, - USPrivacy: dummyCCPA, - } - resolvedUserSyncURL, err := macros.ResolveMacros(*userSyncTemplate, dummyMacroValues) - if err != nil { - return append(errs, fmt.Errorf("Unable to resolve user sync URL: %s for adapter: %s. %v", userSyncURL, adapterName, err)) - } - // Validate the resolved user sync URL - // - // Validating using both IsURL and IsRequestURL because IsURL allows relative paths - // whereas IsRequestURL requires absolute path but fails to check other valid URL - // format constraints. - // - // For example: IsURL will allow "abcd.com" but IsRequestURL won't - // IsRequestURL will allow "http://http://abcd.com" but IsURL won't - if !validator.IsURL(resolvedUserSyncURL) || !validator.IsRequestURL(resolvedUserSyncURL) { - errs = append(errs, fmt.Errorf("The user_sync URL: %s for %s is invalid", resolvedUserSyncURL, adapterName)) - } - } - return errs -} diff --git a/config/bidderinfo.go b/config/bidderinfo.go index 9eff288f64f..750604c31b3 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -12,37 +12,200 @@ import ( // BidderInfos contains a mapping of bidder name to bidder info. type BidderInfos map[string]BidderInfo -// BidderInfo is the maintainer information, supported auction types, and feature opts-in for a bidder. +// BidderInfo specifies all configuration for a bidder except for enabled status, endpoint, and extra information. type BidderInfo struct { Enabled bool // copied from adapter config for convenience. to be refactored. Maintainer *MaintainerInfo `yaml:"maintainer"` Capabilities *CapabilitiesInfo `yaml:"capabilities"` ModifyingVastXmlAllowed bool `yaml:"modifyingVastXmlAllowed"` - Debug *DebugInfo `yaml:"debug,omitempty"` - GVLVendorID uint16 `yaml:"gvlVendorID,omitempty"` + Debug *DebugInfo `yaml:"debug"` + GVLVendorID uint16 `yaml:"gvlVendorID"` + Syncer *Syncer `yaml:"userSync"` } -// MaintainerInfo is the support email address for a bidder. +// MaintainerInfo specifies the support email address for a bidder. type MaintainerInfo struct { Email string `yaml:"email"` } -// CapabilitiesInfo is the supported platforms for a bidder. +// CapabilitiesInfo specifies the supported platforms for a bidder. type CapabilitiesInfo struct { App *PlatformInfo `yaml:"app"` Site *PlatformInfo `yaml:"site"` } -// PlatformInfo is the supported media types for a bidder. +// PlatformInfo specifies the supported media types for a bidder. type PlatformInfo struct { MediaTypes []openrtb_ext.BidType `yaml:"mediaTypes"` } -// DebugInfo is the supported debug options for a bidder. +// DebugInfo specifies the supported debug options for a bidder. type DebugInfo struct { Allow bool `yaml:"allow"` } +// Syncer specifies the user sync settings for a bidder. This struct is shared by the account config, +// so it needs to have both yaml and mapstructure mappings. +type Syncer struct { + // Key is used as the record key for the user sync cookie. We recommend using the bidder name + // as the key for consistency, but that is not enforced as a requirement. + Key string `yaml:"key" mapstructure:"key"` + + // Default identifies which endpoint is preferred if both are allowed by the publisher. This is + // only required if there is more than one endpoint configured for the bidder. Valid values are + // `iframe` and `redirect`. + Default string `yaml:"default" mapstructure:"default"` + + // Supports allows bidders to specify which user sync endpoints they support but which don't have + // good defaults. Host companies should contact the bidder for the endpoint configuration. Hosts + // may not override this value. + Supports []string `yaml:"supports"` + + // IFrame configures an iframe endpoint for user syncing. + IFrame *SyncerEndpoint `yaml:"iframe" mapstructure:"iframe"` + + // Redirect configures an redirect endpoint for user syncing. This is also known as an image + // endpoint in the Prebid.js project. + Redirect *SyncerEndpoint `yaml:"redirect" mapstructure:"redirect"` + + // SupportCORS identifies if CORS is supported for the user syncing endpoints. + SupportCORS *bool `yaml:"supportCors" mapstructure:"support_cors"` +} + +// Override returns a new Syncer object where values in the original are replaced by non-empty/non-default +// values in the override, except for the Supports field which may not be overridden. No changes are made +// to the original or override Syncer. +func (s *Syncer) Override(original *Syncer) *Syncer { + if s == nil && original == nil { + return nil + } + + var copy Syncer + if original != nil { + copy = *original + } + + if s == nil { + return © + } + + if s.Key != "" { + copy.Key = s.Key + } + + if s.Default != "" { + copy.Default = s.Default + } + + if original == nil { + copy.IFrame = s.IFrame.Override(nil) + } else { + copy.IFrame = s.IFrame.Override(original.IFrame) + } + + if original == nil { + copy.Redirect = s.Redirect.Override(nil) + } else { + copy.Redirect = s.Redirect.Override(original.Redirect) + } + + if s.SupportCORS != nil { + copy.SupportCORS = s.SupportCORS + } + + return © +} + +// SyncerEndpoint specifies the configuration of the URL returned by the /cookie_sync endpoint +// for a specific bidder. Bidders must specify at least one endpoint configuration to be eligible +// for selection during a user sync request. +// +// URL is the only required field, although we highly recommend to use the available macros to +// make the configuration readable and maintainable. User sync urls include a redirect url back to +// Prebid Server which is url escaped and can be very diffcult for humans to read. +// +// In most cases, bidders will specify a URL with a `{{.RedirectURL}}` macro for the call back to +// Prebid Server and a UserMacro which the bidder server will replace with the user's id. Example: +// +// url: "https://sync.bidderserver.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" +// userMacro: "$UID" +// +// Prebid Server is configured with a default RedirectURL template matching the /setuid call. This +// may be overridden for all bidders with the `user_sync.redirect_url` host configuration or for a +// specific bidder with the RedirectURL value in this struct. +type SyncerEndpoint struct { + // URL is the endpoint on the bidder server the user will be redirected to when a user sync is + // requested. The following macros are resolved at application startup: + // + // {{.RedirectURL}} - This will be replaced with a redirect url generated using the RedirectURL + // template and url escaped for safe inclusion in any part of the URL. + // + // The following macros are specific to individual requests and are resolved at runtime using the + // Go template engine. For more information on Go templates, see: https://golang.org/pkg/text/template/ + // + // {{.GDPR}} - This will be replaced with the "gdpr" property sent to /cookie_sync. + // {{.Consent}} - This will be replaced with the "consent" property sent to /cookie_sync. + // {{.USPrivacy}} - This will be replaced with the "us_privacy" property sent to /cookie_sync. + URL string `yaml:"url" mapstructure:"url"` + + // RedirectURL is an endpoint on the host server the user will be redirected to when a user sync + // request has been completed by the bidder server. The following macros are resolved at application + // startup: + // + // {{.ExternalURL}} - This will be replaced with the host server's externally reachable http path. + // {{.SyncerKey}} - This will be replaced with the syncer key. + // {{.SyncType}} - This will be replaced with the sync type, either 'b' for iframe syncs or 'i' + // for redirect/image syncs. + // {{.UserMacro}} - This will be replaced with the bidder server's user id macro. + // + // The endpoint on the host server is usually Prebid Server's /setuid endpoint. The default value is: + // `{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&f={{.SyncType}}&uid={{.UserMacro}}` + RedirectURL string `yaml:"redirectUrl" mapstructure:"redirect_url"` + + // ExternalURL is available as a macro to the RedirectURL template. If not specified, the host configuration + // value is used. + ExternalURL string `yaml:"externalUrl" mapstructure:"external_url"` + + // UserMacro is available as a macro to the RedirectURL template. This value is specific to the bidder server + // and has no default. + UserMacro string `yaml:"userMacro" mapstructure:"user_macro"` +} + +// Override returns a new SyncerEndpoint object where values in the original are replaced by non-empty/non-default +// values in the override. No changes are made to the original or override SyncerEndpoint. +func (s *SyncerEndpoint) Override(original *SyncerEndpoint) *SyncerEndpoint { + if s == nil && original == nil { + return nil + } + + var copy SyncerEndpoint + if original != nil { + copy = *original + } + + if s == nil { + return © + } + + if s.URL != "" { + copy.URL = s.URL + } + + if s.RedirectURL != "" { + copy.RedirectURL = s.RedirectURL + } + + if s.ExternalURL != "" { + copy.ExternalURL = s.ExternalURL + } + + if s.UserMacro != "" { + copy.UserMacro = s.UserMacro + } + + return © +} + // LoadBidderInfoFromDisk parses all static/bidder-info/{bidder}.yaml files from the file system. func LoadBidderInfoFromDisk(path string, adapterConfigs map[string]Adapter, bidders []string) (BidderInfos, error) { reader := infoReaderFromDisk{path} diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go index 62a0b853abd..19abdc43f59 100644 --- a/config/bidderinfo_test.go +++ b/config/bidderinfo_test.go @@ -10,24 +10,15 @@ import ( ) const testInfoFilesPath = "./test/bidder-info" -const testYAML = ` +const testSimpleYAML = ` maintainer: email: "some-email@domain.com" gvlVendorID: 42 -capabilities: - app: - mediaTypes: - - banner - - native - site: - mediaTypes: - - banner - - video - - native ` func TestLoadBidderInfoFromDisk(t *testing.T) { bidder := "someBidder" + trueValue := true adapterConfigs := make(map[string]Adapter) adapterConfigs[strings.ToLower(bidder)] = Adapter{} @@ -52,6 +43,23 @@ func TestLoadBidderInfoFromDisk(t *testing.T) { MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, }, }, + Syncer: &Syncer{ + Key: "foo", + Default: "iframe", + IFrame: &SyncerEndpoint{ + URL: "https://foo.com/sync?mode=iframe&r={{.RedirectURL}}", + RedirectURL: "{{.ExternalURL}}/setuid/iframe", + ExternalURL: "https://iframe.host", + UserMacro: "%UID", + }, + Redirect: &SyncerEndpoint{ + URL: "https://foo.com/sync?mode=redirect&r={{.RedirectURL}}", + RedirectURL: "{{.ExternalURL}}/setuid/redirect", + ExternalURL: "https://redirect.host", + UserMacro: "#UID", + }, + SupportCORS: &trueValue, + }, }, } assert.Equal(t, expected, infos) @@ -71,7 +79,7 @@ func TestLoadBidderInfo(t *testing.T) { { description: "Enabled", givenConfigs: map[string]Adapter{strings.ToLower(bidder): {}}, - givenContent: testYAML, + givenContent: testSimpleYAML, expectedInfo: map[string]BidderInfo{ bidder: { Enabled: true, @@ -79,21 +87,13 @@ func TestLoadBidderInfo(t *testing.T) { Email: "some-email@domain.com", }, GVLVendorID: 42, - Capabilities: &CapabilitiesInfo{ - App: &PlatformInfo{ - MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, - }, - Site: &PlatformInfo{ - MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, - }, - }, }, }, }, { description: "Disabled - Bidder Not Configured", givenConfigs: map[string]Adapter{}, - givenContent: testYAML, + givenContent: testSimpleYAML, expectedInfo: map[string]BidderInfo{ bidder: { Enabled: false, @@ -101,21 +101,13 @@ func TestLoadBidderInfo(t *testing.T) { Email: "some-email@domain.com", }, GVLVendorID: 42, - Capabilities: &CapabilitiesInfo{ - App: &PlatformInfo{ - MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, - }, - Site: &PlatformInfo{ - MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, - }, - }, }, }, }, { description: "Disabled - Bidder Wrong Case", givenConfigs: map[string]Adapter{bidder: {}}, - givenContent: testYAML, + givenContent: testSimpleYAML, expectedInfo: map[string]BidderInfo{ bidder: { Enabled: false, @@ -123,21 +115,13 @@ func TestLoadBidderInfo(t *testing.T) { Email: "some-email@domain.com", }, GVLVendorID: 42, - Capabilities: &CapabilitiesInfo{ - App: &PlatformInfo{ - MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, - }, - Site: &PlatformInfo{ - MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, - }, - }, }, }, }, { description: "Disabled - Explicitly Configured", givenConfigs: map[string]Adapter{strings.ToLower(bidder): {Disabled: false}}, - givenContent: testYAML, + givenContent: testSimpleYAML, expectedInfo: map[string]BidderInfo{ bidder: { Enabled: true, @@ -145,14 +129,6 @@ func TestLoadBidderInfo(t *testing.T) { Email: "some-email@domain.com", }, GVLVendorID: 42, - Capabilities: &CapabilitiesInfo{ - App: &PlatformInfo{ - MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, - }, - Site: &PlatformInfo{ - MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, - }, - }, }, }, }, @@ -184,6 +160,167 @@ func TestLoadBidderInfo(t *testing.T) { } } +func TestSyncerOverride(t *testing.T) { + var ( + trueValue = true + falseValue = false + ) + + testCases := []struct { + description string + givenOriginal *Syncer + givenOverride *Syncer + expected *Syncer + }{ + { + description: "Nil", + givenOriginal: nil, + givenOverride: nil, + expected: nil, + }, + { + description: "Original Nil", + givenOriginal: nil, + givenOverride: &Syncer{Key: "anyKey"}, + expected: &Syncer{Key: "anyKey"}, + }, + { + description: "Original Empty", + givenOriginal: &Syncer{}, + givenOverride: &Syncer{Key: "anyKey"}, + expected: &Syncer{Key: "anyKey"}, + }, + { + description: "Override Nil", + givenOriginal: &Syncer{Key: "anyKey"}, + givenOverride: nil, + expected: &Syncer{Key: "anyKey"}, + }, + { + description: "Override Empty", + givenOriginal: &Syncer{Key: "anyKey"}, + givenOverride: &Syncer{}, + expected: &Syncer{Key: "anyKey"}, + }, + { + description: "Override Key", + givenOriginal: &Syncer{Key: "original"}, + givenOverride: &Syncer{Key: "override"}, + expected: &Syncer{Key: "override"}, + }, + { + description: "Override Default", + givenOriginal: &Syncer{Default: "original"}, + givenOverride: &Syncer{Default: "override"}, + expected: &Syncer{Default: "override"}, + }, + { + description: "Override IFrame", + givenOriginal: &Syncer{IFrame: &SyncerEndpoint{URL: "original"}}, + givenOverride: &Syncer{IFrame: &SyncerEndpoint{URL: "override"}}, + expected: &Syncer{IFrame: &SyncerEndpoint{URL: "override"}}, + }, + { + description: "Override Redirect", + givenOriginal: &Syncer{Redirect: &SyncerEndpoint{URL: "original"}}, + givenOverride: &Syncer{Redirect: &SyncerEndpoint{URL: "override"}}, + expected: &Syncer{Redirect: &SyncerEndpoint{URL: "override"}}, + }, + { + description: "Override SupportCORS", + givenOriginal: &Syncer{SupportCORS: &trueValue}, + givenOverride: &Syncer{SupportCORS: &falseValue}, + expected: &Syncer{SupportCORS: &falseValue}, + }, + { + description: "Override Partial - Other Fields Untouched", + givenOriginal: &Syncer{Key: "originalKey", Default: "originalDefault"}, + givenOverride: &Syncer{Default: "overrideDefault"}, + expected: &Syncer{Key: "originalKey", Default: "overrideDefault"}, + }, + } + + for _, test := range testCases { + result := test.givenOverride.Override(test.givenOriginal) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestSyncerEndpointOverride(t *testing.T) { + testCases := []struct { + description string + givenOriginal *SyncerEndpoint + givenOverride *SyncerEndpoint + expected *SyncerEndpoint + }{ + { + description: "Nil", + givenOriginal: nil, + givenOverride: nil, + expected: nil, + }, + { + description: "Original Nil", + givenOriginal: nil, + givenOverride: &SyncerEndpoint{URL: "anyURL"}, + expected: &SyncerEndpoint{URL: "anyURL"}, + }, + { + description: "Original Empty", + givenOriginal: &SyncerEndpoint{}, + givenOverride: &SyncerEndpoint{URL: "anyURL"}, + expected: &SyncerEndpoint{URL: "anyURL"}, + }, + { + description: "Override Nil", + givenOriginal: &SyncerEndpoint{URL: "anyURL"}, + givenOverride: nil, + expected: &SyncerEndpoint{URL: "anyURL"}, + }, + { + description: "Override Empty", + givenOriginal: &SyncerEndpoint{URL: "anyURL"}, + givenOverride: &SyncerEndpoint{}, + expected: &SyncerEndpoint{URL: "anyURL"}, + }, + { + description: "Override URL", + givenOriginal: &SyncerEndpoint{URL: "original"}, + givenOverride: &SyncerEndpoint{URL: "override"}, + expected: &SyncerEndpoint{URL: "override"}, + }, + { + description: "Override RedirectURL", + givenOriginal: &SyncerEndpoint{RedirectURL: "original"}, + givenOverride: &SyncerEndpoint{RedirectURL: "override"}, + expected: &SyncerEndpoint{RedirectURL: "override"}, + }, + { + description: "Override ExternalURL", + givenOriginal: &SyncerEndpoint{ExternalURL: "original"}, + givenOverride: &SyncerEndpoint{ExternalURL: "override"}, + expected: &SyncerEndpoint{ExternalURL: "override"}, + }, + { + description: "Override UserMacro", + givenOriginal: &SyncerEndpoint{UserMacro: "original"}, + givenOverride: &SyncerEndpoint{UserMacro: "override"}, + expected: &SyncerEndpoint{UserMacro: "override"}, + }, + { + description: "Override", + givenOriginal: &SyncerEndpoint{URL: "originalURL", RedirectURL: "originalRedirectURL", ExternalURL: "originalExternalURL", UserMacro: "originalUserMacro"}, + givenOverride: &SyncerEndpoint{URL: "overideURL", RedirectURL: "overideRedirectURL", ExternalURL: "overideExternalURL", UserMacro: "overideUserMacro"}, + expected: &SyncerEndpoint{URL: "overideURL", RedirectURL: "overideRedirectURL", ExternalURL: "overideExternalURL", UserMacro: "overideUserMacro"}, + }, + } + + for _, test := range testCases { + result := test.givenOverride.Override(test.givenOriginal) + assert.Equal(t, test.expected, result, test.description) + } +} + type fakeInfoReader struct { content string err error diff --git a/config/bidderinfo_validate_test.go b/config/bidderinfo_validate_test.go index 23d6e3df034..6a1b18ab092 100644 --- a/config/bidderinfo_validate_test.go +++ b/config/bidderinfo_validate_test.go @@ -1,14 +1,17 @@ -package config +package config_test import ( "errors" "fmt" "io/ioutil" "os" + "path/filepath" "strings" "testing" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/usersync" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v2" ) @@ -35,7 +38,8 @@ func TestBidderInfoFiles(t *testing.T) { expectedFileInfosLength := len(openrtb_ext.CoreBidderNames()) assert.Len(t, fileInfos, expectedFileInfosLength, "static/bidder-info contains %d files, but there are %d known bidders. Did you forget to add a YAML file for your bidder?", len(fileInfos), expectedFileInfosLength) - // Validate Contents + // Load & Validate Contents + bidderInfos := make(config.BidderInfos) for _, fileInfo := range fileInfos { path := fmt.Sprintf(bidderInfoRelativePath + "/" + fileInfo.Name()) @@ -45,16 +49,25 @@ func TestBidderInfoFiles(t *testing.T) { content, err := ioutil.ReadAll(infoFileData) assert.NoError(t, err, "Failed to read static/bidder-info/%s: %v", fileInfo.Name(), err) - var fileInfoContent BidderInfo + var fileInfoContent config.BidderInfo err = yaml.Unmarshal(content, &fileInfoContent) assert.NoError(t, err, "Error interpreting content from static/bidder-info/%s: %v", fileInfo.Name(), err) err = validateInfo(&fileInfoContent) assert.NoError(t, err, "Invalid content in static/bidder-info/%s: %v", fileInfo.Name(), err) + + err = validateSyncer(fileInfoContent) + assert.NoError(t, err, "Invalid syncer config in static/bidder-info/%s: %v", fileInfo.Name(), err) + + fileNameWithoutExtension := fileInfo.Name()[:len(fileInfo.Name())-len(filepath.Ext(fileInfo.Name()))] + bidderInfos[fileNameWithoutExtension] = fileInfoContent } + + errs := validateSyncers(t, bidderInfos) + assert.Empty(t, errs, "syncer errors") } -func validateInfo(info *BidderInfo) error { +func validateInfo(info *config.BidderInfo) error { if err := validateMaintainer(info.Maintainer); err != nil { return err } @@ -66,14 +79,14 @@ func validateInfo(info *BidderInfo) error { return nil } -func validateMaintainer(info *MaintainerInfo) error { +func validateMaintainer(info *config.MaintainerInfo) error { if info == nil || info.Email == "" { return errors.New("missing required field: maintainer.email") } return nil } -func validateCapabilities(info *CapabilitiesInfo) error { +func validateCapabilities(info *config.CapabilitiesInfo) error { if info == nil { return errors.New("missing required field: capabilities") } @@ -96,7 +109,7 @@ func validateCapabilities(info *CapabilitiesInfo) error { return nil } -func validatePlatformInfo(info *PlatformInfo) error { +func validatePlatformInfo(info *config.PlatformInfo) error { if info == nil { return errors.New("object cannot be empty") } @@ -113,3 +126,35 @@ func validatePlatformInfo(info *PlatformInfo) error { return nil } + +func validateSyncers(t *testing.T, bidderInfos config.BidderInfos) []error { + hostConfig := &config.Configuration{ + UserSync: config.UserSync{ + ExternalURL: "http://host.com", + RedirectURL: "{{.ExternalURL}}/host", + }, + } + + // enable all bidders to allow BuildSyncers to build all syncers + for k, v := range bidderInfos { + v.Enabled = true + bidderInfos[k] = v + } + + _, errs := usersync.BuildSyncers(hostConfig, bidderInfos) + return errs +} + +func validateSyncer(bidderInfo config.BidderInfo) error { + if bidderInfo.Syncer == nil { + return nil + } + + for _, v := range bidderInfo.Syncer.Supports { + if !strings.EqualFold(v, "iframe") && !strings.EqualFold(v, "redirect") { + return fmt.Errorf("syncer could not be created, invalid supported endpoint: %s", v) + } + } + + return nil +} diff --git a/config/config.go b/config/config.go index 224cf85931b..f1ed1ff0a98 100644 --- a/config/config.go +++ b/config/config.go @@ -40,6 +40,7 @@ type Configuration struct { VTrack VTrack `mapstructure:"vtrack"` Event Event `mapstructure:"event"` Accounts StoredRequests `mapstructure:"accounts"` + UserSync UserSync `mapstructure:"user_sync"` // Note that StoredVideo refers to stored video requests, and has nothing to do with caching video creatives. StoredVideo StoredRequests `mapstructure:"stored_video_req"` @@ -484,7 +485,6 @@ func New(v *viper.Viper) (*Configuration, error) { if err := v.Unmarshal(&c); err != nil { return nil, fmt.Errorf("viper failed to unmarshal app config: %v", err) } - c.setDerivedDefaults() if err := c.RequestValidation.Parse(); err != nil { return nil, err @@ -594,122 +594,6 @@ func (cfg *Configuration) GetCachedAssetURL(uuid string) string { return fmt.Sprintf("%s/cache?%s", cfg.CacheURL.GetBaseURL(), strings.Replace(cfg.CacheURL.Query, "%PBS_CACHE_UUID%", uuid, 1)) } -// Initialize any default config values which have sensible defaults, but those defaults depend on other config values. -// -// For example, the typical Bidder's usersync URL includes the PBS config.external_url, because it redirects to the `external_url/setuid` endpoint. -// -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") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernelAdn, "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdpone, "https://usersync.adpone.com/csync?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadpone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") - // openrtb_ext.BidderAdtarget doesn't have a good default. - // openrtb_ext.BidderAdtelligent doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdman, "https://sync.admanmedia.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadman%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") - // openrtb_ext.BidderAdOcean doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - // openrtb_ext.BidderAdxcg doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdyoulike, "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadyoulike%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BBUYER_USERID%5D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAJA, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Daja%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25s") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAMX, "https://prebid.a-mo.net/cchain/0?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Damx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAppnexus, "https://ib.adnxs.com/getuid?"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAvocet, "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+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") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") - // openrtb_ext.BidderDecenterAds doesn't have a good default. - // openrtb_ext.BidderDMX doesn't have a good default. - 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. - // openrtb_ext.BidderFacebook doesn't have a good default. - // openrtb_ext.BidderGamma doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGamoshi, "https://rtb.gamoshi.io/user_sync_prebid?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgamoshi%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bgusr%5D") - 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://sync.inmobi.com/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+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=194962&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.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") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNanoInteractive, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+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") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPulsepoint, "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderRhythmone, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D") - // openrtb_ext.BidderRTBHouse doesn't have a good default. - // openrtb_ext.BidderRubicon doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSharethrough, "https://match.sharethrough.com/FGMrCMMc/v1?redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsharethrough%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - 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") - // openrtb_ext.BidderSmartHub doesn't have a good default. - 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") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSynacormedia, "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTappx, "https://ssp.api.tappx.com/cs/usersync.php?gdpr_optin={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&type=iframe&ruid="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BTPPXUID%7D%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTelaria, "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtelaria%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Btvid%5D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTrustX, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtrustx%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.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") - // openrtb_ext.BidderUnicorn doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") - // openrtb_ext.BidderVrtcal doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldlab, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_cs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBetween, "https://ads.betweendigital.com/match?bidder_id=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbetween%26gdpr%3D0%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_ID%7D") -} - -func setDefaultUsersync(m map[string]Adapter, bidder openrtb_ext.BidderName, defaultValue string) { - lowercased := strings.ToLower(string(bidder)) - if m[lowercased].UserSyncURL == "" { - // Go doesnt let us edit the properties of a value inside a map directly. - editable := m[lowercased] - editable.UserSyncURL = defaultValue - m[lowercased] = editable - } -} - // Set the default config values for the viper object we are using. func SetupViper(v *viper.Viper, filename string) { if filename != "" { @@ -841,6 +725,10 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("accounts.filesystem.directorypath", "./stored_requests/data/by_id") v.SetDefault("accounts.in_memory_cache.type", "none") + // some adapters append the user id to the end of the redirect url instead of using + // macro substitution. it is important for the uid to be the last query parameter. + v.SetDefault("user_sync.redirect_url", "{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&f={{.SyncType}}&uid={{.UserMacro}}") + for _, bidder := range openrtb_ext.CoreBidderNames() { setBidderDefaults(v, strings.ToLower(string(bidder))) } @@ -1115,17 +1003,29 @@ func migrateConfigPurposeOneTreatment(v *viper.Viper) { } func setBidderDefaults(v *viper.Viper, bidder string) { - adapterCfgPrefix := "adapters." - v.SetDefault(adapterCfgPrefix+bidder+".endpoint", "") - v.SetDefault(adapterCfgPrefix+bidder+".usersync_url", "") - v.SetDefault(adapterCfgPrefix+bidder+".platform_id", "") - v.SetDefault(adapterCfgPrefix+bidder+".app_secret", "") - v.SetDefault(adapterCfgPrefix+bidder+".xapi.username", "") - v.SetDefault(adapterCfgPrefix+bidder+".xapi.password", "") - v.SetDefault(adapterCfgPrefix+bidder+".xapi.tracker", "") - v.SetDefault(adapterCfgPrefix+bidder+".disabled", false) - v.SetDefault(adapterCfgPrefix+bidder+".partner_id", "") - v.SetDefault(adapterCfgPrefix+bidder+".extra_info", "") + adapterCfgPrefix := "adapters." + bidder + v.SetDefault(adapterCfgPrefix+".endpoint", "") + v.SetDefault(adapterCfgPrefix+".usersync_url", "") + v.SetDefault(adapterCfgPrefix+".platform_id", "") + v.SetDefault(adapterCfgPrefix+".app_secret", "") + v.SetDefault(adapterCfgPrefix+".xapi.username", "") + v.SetDefault(adapterCfgPrefix+".xapi.password", "") + v.SetDefault(adapterCfgPrefix+".xapi.tracker", "") + v.SetDefault(adapterCfgPrefix+".disabled", false) + v.SetDefault(adapterCfgPrefix+".partner_id", "") + v.SetDefault(adapterCfgPrefix+".extra_info", "") + + v.BindEnv(adapterCfgPrefix + ".usersync.key") + v.BindEnv(adapterCfgPrefix + ".usersync.default") + v.BindEnv(adapterCfgPrefix + ".usersync.iframe.url") + v.BindEnv(adapterCfgPrefix + ".usersync.iframe.redirect_url") + v.BindEnv(adapterCfgPrefix + ".usersync.iframe.external_url") + v.BindEnv(adapterCfgPrefix + ".usersync.iframe.user_macro") + v.BindEnv(adapterCfgPrefix + ".usersync.redirect.url") + v.BindEnv(adapterCfgPrefix + ".usersync.redirect.redirect_url") + v.BindEnv(adapterCfgPrefix + ".usersync.redirect.external_url") + v.BindEnv(adapterCfgPrefix + ".usersync.redirect.user_macro") + v.BindEnv(adapterCfgPrefix + ".usersync.support_cors") } func isValidCookieSize(maxCookieSize int) error { diff --git a/config/config_test.go b/config/config_test.go index a87d65af359..287d57ede26 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -388,20 +388,6 @@ adapters: usersync_url: https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r= `) -var invalidUserSyncURLConfig = []byte(` -adapters: - appnexus: - endpoint: http://ib.adnxs.com/some/endpoint - audienceNetwork: - endpoint: http://facebook.com/pbs - usersync_url: http://facebook.com/ortb/prebid-s2s - platform_id: abcdefgh1234 - brightroll: - usersync_url: http//test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&url=%s - adkerneladn: - usersync_url: http:\\tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r= -`) - var oldStoredRequestsConfig = []byte(` stored_requests: filesystem: true @@ -577,21 +563,16 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "adapters.appnexus.endpoint", cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, "http://ib.adnxs.com/some/endpoint") cmpStrings(t, "adapters.appnexus.extra_info", cfg.Adapters[string(openrtb_ext.BidderAppnexus)].ExtraAdapterInfo, "{\"native\":\"http://www.native.org/endpoint\",\"video\":\"http://www.video.org/endpoint\"}") cmpStrings(t, "adapters.audiencenetwork.endpoint", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAudienceNetwork))].Endpoint, "http://facebook.com/pbs") - cmpStrings(t, "adapters.audiencenetwork.usersync_url", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAudienceNetwork))].UserSyncURL, "http://facebook.com/ortb/prebid-s2s") cmpStrings(t, "adapters.audiencenetwork.platform_id", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAudienceNetwork))].PlatformID, "abcdefgh1234") cmpStrings(t, "adapters.audiencenetwork.app_secret", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAudienceNetwork))].AppSecret, "987abc") cmpStrings(t, "adapters.beachfront.endpoint", cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, "https://display.bfmio.com/prebid_display") cmpStrings(t, "adapters.beachfront.extra_info", cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo, "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") cmpStrings(t, "adapters.ix.endpoint", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint, "http://ixtest.com/api") cmpStrings(t, "adapters.rubicon.endpoint", cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, "http://rubitest.com/api") - cmpStrings(t, "adapters.rubicon.usersync_url", cfg.Adapters[string(openrtb_ext.BidderRubicon)].UserSyncURL, "http://pixel.rubiconproject.com/sync.php?p=prebid") cmpStrings(t, "adapters.rubicon.xapi.username", cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Username, "rubiuser") cmpStrings(t, "adapters.rubicon.xapi.password", cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Password, "rubipw23") cmpStrings(t, "adapters.brightroll.endpoint", cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint, "http://test-bid.ybp.yahoo.com/bid/appnexuspbs") - cmpStrings(t, "adapters.brightroll.usersync_url", cfg.Adapters[string(openrtb_ext.BidderBrightroll)].UserSyncURL, "http://test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url=%s") - cmpStrings(t, "adapters.adkerneladn.usersync_url", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAdkernelAdn))].UserSyncURL, "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r=") cmpStrings(t, "adapters.rhythmone.endpoint", cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint, "http://tag.1rx.io/rmp") - cmpStrings(t, "adapters.rhythmone.usersync_url", cfg.Adapters[string(openrtb_ext.BidderRhythmone)].UserSyncURL, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=http%3A%2F%2Fprebid-server.prebid.org%2F%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D") cmpBools(t, "account_required", cfg.AccountRequired, true) cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, false) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true) @@ -779,16 +760,6 @@ func TestInvalidAdapterEndpointConfig(t *testing.T) { assert.Error(t, err, "invalid endpoint in config should return an error") } -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) - assert.Error(t, err, "invalid user_sync URL in config should return an error") -} - func TestNegativeRequestSize(t *testing.T) { cfg, v := newDefaultConfig(t) cfg.MaxRequestSize = -1 diff --git a/config/test/bidder-info/someBidder.yaml b/config/test/bidder-info/someBidder.yaml index 232b73d0aac..0a1b3059cd4 100644 --- a/config/test/bidder-info/someBidder.yaml +++ b/config/test/bidder-info/someBidder.yaml @@ -11,3 +11,17 @@ capabilities: - banner - video - native +userSync: + key: "foo" + default: "iframe" + iframe: + url: "https://foo.com/sync?mode=iframe&r={{.RedirectURL}}" + redirectUrl: "{{.ExternalURL}}/setuid/iframe" + externalUrl: "https://iframe.host" + userMacro: "%UID" + redirect: + url: "https://foo.com/sync?mode=redirect&r={{.RedirectURL}}" + redirectUrl: "{{.ExternalURL}}/setuid/redirect" + externalUrl: "https://redirect.host" + userMacro: "#UID" + supportCors: true \ No newline at end of file diff --git a/config/usersync.go b/config/usersync.go new file mode 100644 index 00000000000..27badbcb815 --- /dev/null +++ b/config/usersync.go @@ -0,0 +1,14 @@ +package config + +// UserSync specifies the static global user sync configuration. +type UserSync struct { + Cooperative UserSyncCooperative `mapstructure:"coop_sync"` + ExternalURL string `mapstructure:"external_url"` + RedirectURL string `mapstructure:"redirect_url"` +} + +// UserSyncCooperative specifies the static global default cooperative cookie sync +type UserSyncCooperative struct { + EnabledByDefault bool `mapstructure:"default"` + PriorityGroups [][]string `mapstructure:"priority_groups"` +} diff --git a/endpoints/auction.go b/endpoints/auction.go index f76f8237660..10d2ced6c37 100644 --- a/endpoints/auction.go +++ b/endpoints/auction.go @@ -27,6 +27,8 @@ import ( "github.com/prebid/prebid-server/usersync" ) +var allSyncTypes []usersync.SyncType = []usersync.SyncType{usersync.SyncTypeIFrame, usersync.SyncTypeRedirect} + type bidResult struct { bidder *pbs.PBSBidder bidList pbs.PBSBidSlice @@ -57,22 +59,22 @@ func writeAuctionError(w http.ResponseWriter, s string, err error) { } type auction struct { - cfg *config.Configuration - syncers map[openrtb_ext.BidderName]usersync.Usersyncer - gdprPerms gdpr.Permissions - metricsEngine metrics.MetricsEngine - dataCache cache.Cache - exchanges map[string]adapters.Adapter + cfg *config.Configuration + syncersByBidder map[string]usersync.Syncer + gdprPerms gdpr.Permissions + metricsEngine metrics.MetricsEngine + dataCache cache.Cache + exchanges map[string]adapters.Adapter } -func Auction(cfg *config.Configuration, syncers map[openrtb_ext.BidderName]usersync.Usersyncer, gdprPerms gdpr.Permissions, metricsEngine metrics.MetricsEngine, dataCache cache.Cache, exchanges map[string]adapters.Adapter) httprouter.Handle { +func Auction(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPerms gdpr.Permissions, metricsEngine metrics.MetricsEngine, dataCache cache.Cache, exchanges map[string]adapters.Adapter) httprouter.Handle { a := &auction{ - cfg: cfg, - syncers: syncers, - gdprPerms: gdprPerms, - metricsEngine: metricsEngine, - dataCache: dataCache, - exchanges: exchanges, + cfg: cfg, + syncersByBidder: syncersByBidder, + gdprPerms: gdprPerms, + metricsEngine: metricsEngine, + dataCache: dataCache, + exchanges: exchanges, } return a.auction } @@ -374,11 +376,11 @@ func setLabelSource(labels *metrics.Labels, req *pbs.PBSRequest, status *string) labels.Source = metrics.DemandApp } else { labels.Source = metrics.DemandWeb - if req.Cookie.LiveSyncCount() == 0 { + if req.Cookie.HasAnyLiveSyncs() { + labels.CookieFlag = metrics.CookieFlagYes + } else { labels.CookieFlag = metrics.CookieFlagNo *status = "no_cookie" - } else { - labels.CookieFlag = metrics.CookieFlagYes } } } @@ -480,8 +482,8 @@ func (a *auction) processUserSync(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bl if syncerCode == "districtm" { syncerCode = "appnexus" } - syncer := a.syncers[openrtb_ext.BidderName(syncerCode)] - uid, _, _ := req.Cookie.GetUID(syncer.FamilyName()) + syncer := a.syncersByBidder[syncerCode] + uid, _, _ := req.Cookie.GetUID(syncer.Key()) if uid == "" { bidder.NoCookie = true privacyPolicies := privacy.Policies{ @@ -491,9 +493,13 @@ func (a *auction) processUserSync(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bl }, } if a.shouldUsersync(*ctx, openrtb_ext.BidderName(syncerCode), privacyPolicies.GDPR) { - syncInfo, err := syncer.GetUsersyncInfo(privacyPolicies) + sync, err := syncer.GetSync(allSyncTypes, privacyPolicies) if err == nil { - bidder.UsersyncInfo = syncInfo + bidder.UsersyncInfo = &pbs.UsersyncInfo{ + URL: sync.URL, + Type: string(sync.Type), + SupportCORS: sync.SupportCORS, + } } else { glog.Errorf("Failed to get usersync info for %s: %v", syncerCode, err) } diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index bdf68db5be7..522b38babcf 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -20,7 +20,7 @@ import ( "github.com/prebid/prebid-server/pbs" "github.com/prebid/prebid-server/prebid_cache_client" gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/prebid/prebid-server/usersync/usersyncers" + "github.com/prebid/prebid-server/usersync" "github.com/spf13/viper" "github.com/stretchr/testify/assert" @@ -351,13 +351,13 @@ func TestCacheVideoOnly(t *testing.T) { if err != nil { t.Fatal(err.Error()) } - syncers := usersyncers.NewSyncerMap(cfg) + syncersByBidder := map[string]usersync.Syncer{} gdprPerms := gdpr.NewPermissions(context.Background(), config.GDPR{ HostVendorID: 0, }, nil, nil) prebid_cache_client.InitPrebidCache(server.URL) var labels = &metrics.Labels{} - if err := cacheVideoOnly(bids, ctx, &auction{cfg: cfg, syncers: syncers, gdprPerms: gdprPerms, metricsEngine: &metricsConf.DummyMetricsEngine{}}, labels); err != nil { + if err := cacheVideoOnly(bids, ctx, &auction{cfg: cfg, syncersByBidder: syncersByBidder, gdprPerms: gdprPerms, metricsEngine: &metricsConf.DummyMetricsEngine{}}, labels); err != nil { t.Errorf("Prebid cache failed: %v \n", err) return } @@ -638,8 +638,8 @@ func TestWriteAuctionError(t *testing.T) { func TestPanicRecovery(t *testing.T) { dummy := auction{ - cfg: nil, - syncers: nil, + cfg: nil, + syncersByBidder: nil, gdprPerms: &auctionMockPermissions{ allowBidderSync: false, allowHostCookies: false, diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 3c1354c86bd..f6ec8152870 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -6,11 +6,8 @@ import ( "errors" "fmt" "io/ioutil" - "math/rand" "net/http" - "strconv" - "github.com/buger/jsonparser" "github.com/golang/glog" "github.com/julienschmidt/httprouter" "github.com/prebid/prebid-server/analytics" @@ -24,281 +21,353 @@ import ( "github.com/prebid/prebid-server/usersync" ) +var ( + errCookieSyncOptOut = errors.New("User has opted out") + errCookieSyncBody = errors.New("Failed to read request body") + errCookieSyncGDPRConsentMissing = errors.New("gdpr_consent is required if gdpr=1") + errCookieSyncGDPRConsentMissingSignalAmbiguous = errors.New("gdpr_consent is required. gdpr is not specified and is assumed to be 1 by the server. set gdpr=0 to exempt this request") + errCookieSyncInvalidBiddersType = errors.New("invalid bidders type. must either be a string '*' or a string array of bidders") +) + +var cookieSyncBidderFilterAllowAll = usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude) + func NewCookieSyncEndpoint( - syncers map[openrtb_ext.BidderName]usersync.Usersyncer, - cfg *config.Configuration, - syncPermissions gdpr.Permissions, + syncersByBidder map[string]usersync.Syncer, + config *config.Configuration, + gdprPermissions gdpr.Permissions, metrics metrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, - bidderMap map[string]openrtb_ext.BidderName) httprouter.Handle { + bidders map[string]openrtb_ext.BidderName) HTTPRouterHandler { - bidderLookup := make(map[string]struct{}) - for k := range bidderMap { - bidderLookup[k] = struct{}{} + bidderHashSet := make(map[string]struct{}, len(bidders)) + for _, bidder := range bidders { + bidderHashSet[string(bidder)] = struct{}{} } - deps := &cookieSyncDeps{ - syncers: syncers, - hostCookie: &cfg.HostCookie, - gDPR: &cfg.GDPR, - syncPermissions: syncPermissions, - metrics: metrics, - pbsAnalytics: pbsAnalytics, - enforceCCPA: cfg.CCPA.Enforce, - bidderLookup: bidderLookup, + return &cookieSyncEndpoint{ + chooser: usersync.NewChooser(syncersByBidder), + config: config.UserSync, + hostCookieConfig: &config.HostCookie, + privacyConfig: usersyncPrivacyConfig{ + gdprConfig: config.GDPR, + gdprPermissions: gdprPermissions, + ccpaEnforce: config.CCPA.Enforce, + bidderHashSet: bidderHashSet, + }, + metrics: metrics, + pbsAnalytics: pbsAnalytics, } - return deps.Endpoint } -type cookieSyncDeps struct { - syncers map[openrtb_ext.BidderName]usersync.Usersyncer - hostCookie *config.HostCookie - gDPR *config.GDPR - syncPermissions gdpr.Permissions - metrics metrics.MetricsEngine - pbsAnalytics analytics.PBSAnalyticsModule - enforceCCPA bool - bidderLookup map[string]struct{} +type cookieSyncEndpoint struct { + chooser usersync.Chooser + config config.UserSync + hostCookieConfig *config.HostCookie + privacyConfig usersyncPrivacyConfig + metrics metrics.MetricsEngine + pbsAnalytics analytics.PBSAnalyticsModule } -func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - //CookieSyncObject makes a log of requests and responses to /cookie_sync endpoint - co := analytics.CookieSyncObject{ - Status: http.StatusOK, - Errors: make([]error, 0), - BidderStatus: make([]*usersync.CookieSyncBidders, 0), +func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + request, privacyPolicies, err := c.parseRequest(r) + if err != nil { + c.metrics.RecordCookieSync(metrics.CookieSyncBadRequest) + c.handleError(w, err, http.StatusBadRequest) + return } - defer deps.pbsAnalytics.LogCookieSyncObject(&co) - - deps.metrics.RecordCookieSync() - userSyncCookie := usersync.ParsePBSCookieFromRequest(r, deps.hostCookie) - if !userSyncCookie.AllowSyncs() { - http.Error(w, "User has opted out", http.StatusUnauthorized) - co.Status = http.StatusUnauthorized - co.Errors = append(co.Errors, fmt.Errorf("user has opted out")) - return + cookie := usersync.ParseCookieFromRequest(r, c.hostCookieConfig) + + result := c.chooser.Choose(request, cookie) + switch result.Status { + case usersync.StatusBlockedByUserOptOut: + c.metrics.RecordCookieSync(metrics.CookieSyncOptOut) + c.handleError(w, errCookieSyncOptOut, http.StatusUnauthorized) + case usersync.StatusBlockedByGDPR: + c.metrics.RecordCookieSync(metrics.CookieSyncGDPRHostCookieBlocked) + c.handleResponse(w, request.SyncTypeFilter, cookie, privacyPolicies, nil) + case usersync.StatusOK: + c.metrics.RecordCookieSync(metrics.CookieSyncOK) + c.writeBidderMetrics(result.BiddersEvaluated) + c.handleResponse(w, request.SyncTypeFilter, cookie, privacyPolicies, result.SyncersChosen) } +} +func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, privacy.Policies, error) { defer r.Body.Close() - bodyBytes, err := ioutil.ReadAll(r.Body) + body, err := ioutil.ReadAll(r.Body) if err != nil { - co.Status = http.StatusBadRequest - co.Errors = append(co.Errors, errors.New("Failed to read request body")) - http.Error(w, "Failed to read request body", http.StatusBadRequest) - return - } - biddersJSON, err := parseBidders(bodyBytes) - if err != nil { - co.Status = http.StatusBadRequest - co.Errors = append(co.Errors, errors.New("Failed to check request.bidders in request body. Was your JSON well-formed?")) - http.Error(w, "Failed to check request.bidders in request body. Was your JSON well-formed?", http.StatusBadRequest) - return + return usersync.Request{}, privacy.Policies{}, errCookieSyncBody } - parsedReq := &cookieSyncRequest{} - 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) - return + request := cookieSyncRequest{} + if err := json.Unmarshal(body, &request); err != nil { + return usersync.Request{}, privacy.Policies{}, fmt.Errorf("JSON parsing failed: %s", err.Error()) } - if len(biddersJSON) == 0 { - parsedReq.Bidders = make([]string, 0, len(deps.syncers)) - for bidder := range deps.syncers { - parsedReq.Bidders = append(parsedReq.Bidders, string(bidder)) - } - } - setSiteCookie := siteCookieCheck(r.UserAgent()) - needSyncupForSameSite := false - if setSiteCookie { - _, err1 := r.Cookie(usersync.SameSiteCookieName) - if err1 == http.ErrNoCookie { - needSyncupForSameSite = true - } + gdprSignal, err := gdpr.SignalParse(request.GDPR) + if err != nil { + return usersync.Request{}, privacy.Policies{}, err } - parsedReq.filterExistingSyncs(deps.syncers, userSyncCookie, needSyncupForSameSite) + if request.GDPRConsent == "" { + if gdprSignal == gdpr.SignalYes { + return usersync.Request{}, privacy.Policies{}, errCookieSyncGDPRConsentMissing + } - adapterSyncs := make(map[openrtb_ext.BidderName]bool) - // assume all bidders will be privacy blocked - for _, b := range parsedReq.Bidders { - adapterSyncs[openrtb_ext.BidderName(b)] = true + if gdprSignal == gdpr.SignalAmbiguous && gdpr.SignalNormalize(gdprSignal, c.privacyConfig.gdprConfig) == gdpr.SignalYes { + return usersync.Request{}, privacy.Policies{}, errCookieSyncGDPRConsentMissingSignalAmbiguous + } } - privacyPolicy := privacy.Policies{ + privacyPolicies := privacy.Policies{ GDPR: gdprPrivacy.Policy{ - Signal: gdprToString(parsedReq.GDPR), - Consent: parsedReq.Consent, + Signal: request.GDPR, + Consent: request.GDPRConsent, }, CCPA: ccpa.Policy{ - Consent: parsedReq.USPrivacy, + Consent: request.USPrivacy, }, } - parsedReq.filterForGDPR(deps.syncPermissions) - - if deps.enforceCCPA { - parsedReq.filterForCCPA(deps.bidderLookup) + ccpaParsedPolicy := ccpa.ParsedPolicy{} + if request.USPrivacy != "" { + parsedPolicy, err := privacyPolicies.CCPA.Parse(c.privacyConfig.bidderHashSet) + if err != nil { + privacyPolicies.CCPA.Consent = "" + } + if c.privacyConfig.ccpaEnforce { + ccpaParsedPolicy = parsedPolicy + } } - // surviving bidders are not privacy blocked - for _, b := range parsedReq.Bidders { - adapterSyncs[openrtb_ext.BidderName(b)] = false + syncTypeFilter, err := parseTypeFilter(request.FilterSettings) + if err != nil { + return usersync.Request{}, privacy.Policies{}, err } - for b, g := range adapterSyncs { - deps.metrics.RecordAdapterCookieSync(b, g) + + rx := usersync.Request{ + Bidders: request.Bidders, + Cooperative: usersync.Cooperative{ + Enabled: (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.Cooperative.EnabledByDefault), + PriorityGroups: c.config.Cooperative.PriorityGroups, + }, + Limit: request.Limit, + Privacy: usersyncPrivacy{ + gdprPermissions: c.privacyConfig.gdprPermissions, + gdprSignal: gdprSignal, + gdprConsent: request.GDPRConsent, + ccpaParsedPolicy: ccpaParsedPolicy, + }, + SyncTypeFilter: syncTypeFilter, } - parsedReq.filterToLimit() + return rx, privacyPolicies, nil +} - csResp := cookieSyncResponse{ - Status: cookieSyncStatus(userSyncCookie.LiveSyncCount()), - BidderStatus: make([]*usersync.CookieSyncBidders, 0, len(parsedReq.Bidders)), +func parseTypeFilter(request *cookieSyncRequestFilterSettings) (usersync.SyncTypeFilter, error) { + syncTypeFilter := usersync.SyncTypeFilter{ + IFrame: cookieSyncBidderFilterAllowAll, + Redirect: cookieSyncBidderFilterAllowAll, } - for i := 0; i < len(parsedReq.Bidders); i++ { - bidder := parsedReq.Bidders[i] - syncInfo, err := deps.syncers[openrtb_ext.BidderName(bidder)].GetUsersyncInfo(privacyPolicy) - if err == nil { - newSync := &usersync.CookieSyncBidders{ - BidderCode: bidder, - NoCookie: true, - UsersyncInfo: syncInfo, - } - csResp.BidderStatus = append(csResp.BidderStatus, newSync) + + if request != nil { + if filter, err := parseBidderFilter(request.IFrame); err == nil { + syncTypeFilter.IFrame = filter } else { - glog.Errorf("Failed to get usersync info for %s: %v", bidder, err) + return usersync.SyncTypeFilter{}, fmt.Errorf("error parsing filtersettings.iframe: %v", err) } - } - if len(csResp.BidderStatus) > 0 { - co.BidderStatus = append(co.BidderStatus, csResp.BidderStatus...) + if filter, err := parseBidderFilter(request.Redirect); err == nil { + syncTypeFilter.Redirect = filter + } else { + return usersync.SyncTypeFilter{}, fmt.Errorf("error parsing filtersettings.image: %v", err) + } } - w.Header().Set("Content-Type", "application/json; charset=utf-8") - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - enc.Encode(csResp) + return syncTypeFilter, nil } -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()) +func parseBidderFilter(filter *cookieSyncRequestFilter) (usersync.BidderFilter, error) { + if filter == nil { + return cookieSyncBidderFilterAllowAll, nil } - if parsedReq.GDPR != nil && *parsedReq.GDPR == 1 && parsedReq.Consent == "" { - return errors.New("gdpr_consent is required if gdpr=1") + var mode usersync.BidderFilterMode + switch filter.Mode { + case "include": + mode = usersync.BidderFilterModeInclude + case "exclude": + mode = usersync.BidderFilterModeExclude + default: + return nil, fmt.Errorf("invalid filter value '%s'. must be either 'include' or 'exclude'", filter.Mode) } - // If GDPR is ambiguous, lets untangle it here. - if parsedReq.GDPR == nil { - var gdpr = new(int) - *gdpr = 1 - if gdprDefaultValue == "0" { - *gdpr = 0 + + switch v := filter.Bidders.(type) { + case string: + if v == "*" { + return usersync.NewUniformBidderFilter(mode), nil } - parsedReq.GDPR = gdpr + return nil, fmt.Errorf("invalid bidders value `%s`. must either be '*' or a string array", v) + case []interface{}: + bidders := make([]string, len(v)) + for i, x := range v { + if bidder, ok := x.(string); ok { + bidders[i] = bidder + } else { + return nil, errCookieSyncInvalidBiddersType + } + } + return usersync.NewSpecificBidderFilter(bidders, mode), nil + default: + return nil, errCookieSyncInvalidBiddersType } - return nil } -func gdprToString(gdpr *int) string { - if gdpr == nil { - return "" +func (c *cookieSyncEndpoint) handleError(w http.ResponseWriter, err error, httpStatus int) { + http.Error(w, err.Error(), httpStatus) + c.pbsAnalytics.LogCookieSyncObject(&analytics.CookieSyncObject{ + Status: httpStatus, + Errors: []error{err}, + BidderStatus: []*analytics.CookieSyncBidder{}, + }) +} + +func (c *cookieSyncEndpoint) writeBidderMetrics(biddersEvaluated []usersync.BidderEvaluation) { + for _, bidder := range biddersEvaluated { + switch bidder.Status { + case usersync.StatusOK: + c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncOK) + case usersync.StatusBlockedByGDPR: + c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) + case usersync.StatusBlockedByCCPA: + c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) + case usersync.StatusAlreadySynced: + c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncAlreadySynced) + case usersync.StatusTypeNotSupported: + c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncTypeNotSupported) + } } - return strconv.Itoa(*gdpr) } -func parseBidders(request []byte) ([]byte, error) { - value, valueType, _, err := jsonparser.Get(request, "bidders") - if err == nil && valueType != jsonparser.NotExist { - return value, nil - } else if err != jsonparser.KeyPathNotFoundError { - return nil, err +func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, p privacy.Policies, s []usersync.SyncerChoice) { + status := "no_cookie" + if co.HasAnyLiveSyncs() { + status = "ok" + } + + response := cookieSyncResponse{ + Status: status, + BidderStatus: make([]cookieSyncResponseBidder, 0, len(s)), + } + + for _, syncerChoice := range s { + syncTypes := tf.ForBidder(syncerChoice.Bidder) + sync, err := syncerChoice.Syncer.GetSync(syncTypes, p) + if err != nil { + glog.Errorf("Failed to get usersync info for %s: %v", syncerChoice.Bidder, err) + continue + } + + response.BidderStatus = append(response.BidderStatus, cookieSyncResponseBidder{ + BidderCode: syncerChoice.Bidder, + NoCookie: true, + UsersyncInfo: cookieSyncResponseSync{ + URL: sync.URL, + Type: string(sync.Type), + SupportCORS: sync.SupportCORS, + }, + }) } - return nil, nil + + c.pbsAnalytics.LogCookieSyncObject(&analytics.CookieSyncObject{ + Status: http.StatusOK, + BidderStatus: mapBidderStatusToAnalytics(response.BidderStatus), + }) + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + enc.Encode(response) } -func cookieSyncStatus(syncCount int) string { - if syncCount == 0 { - return "no_cookie" +func mapBidderStatusToAnalytics(from []cookieSyncResponseBidder) []*analytics.CookieSyncBidder { + to := make([]*analytics.CookieSyncBidder, len(from)) + for i, b := range from { + to[i] = &analytics.CookieSyncBidder{ + BidderCode: b.BidderCode, + NoCookie: b.NoCookie, + UsersyncInfo: &analytics.UsersyncInfo{ + URL: b.UsersyncInfo.URL, + Type: b.UsersyncInfo.Type, + SupportCORS: b.UsersyncInfo.SupportCORS, + }, + } } - return "ok" + return to } type cookieSyncRequest struct { - Bidders []string `json:"bidders"` - GDPR *int `json:"gdpr"` - Consent string `json:"gdpr_consent"` - USPrivacy string `json:"us_privacy"` - Limit int `json:"limit"` + Bidders []string `json:"bidders"` + GDPR string `json:"gdpr"` + GDPRConsent string `json:"gdpr_consent"` + USPrivacy string `json:"us_privacy"` + Limit int `json:"limit"` + CooperativeSync *bool `json:"coopSync"` + FilterSettings *cookieSyncRequestFilterSettings `json:"filterSettings"` } -func (req *cookieSyncRequest) filterExistingSyncs(valid map[openrtb_ext.BidderName]usersync.Usersyncer, cookie *usersync.PBSCookie, needSyncupForSameSite bool) { - for i := 0; i < len(req.Bidders); i++ { - thisBidder := req.Bidders[i] - if syncer, isValid := valid[openrtb_ext.BidderName(thisBidder)]; !isValid || (cookie.HasLiveSync(syncer.FamilyName()) && !needSyncupForSameSite) { - req.Bidders = append(req.Bidders[:i], req.Bidders[i+1:]...) - i-- - } - } +type cookieSyncRequestFilterSettings struct { + IFrame *cookieSyncRequestFilter `json:"iframe"` + Redirect *cookieSyncRequestFilter `json:"image"` } -func (req *cookieSyncRequest) filterForGDPR(permissions gdpr.Permissions) { - if req.GDPR != nil && *req.GDPR == 0 { - return - } +type cookieSyncRequestFilter struct { + Bidders interface{} `json:"bidders"` + Mode string `json:"filter"` +} - // At this point we know the gdpr signal is Yes because the upstream call to parseRequest already denormalized the signal if it was ambiguous - if allowSync, err := permissions.HostCookiesAllowed(context.Background(), gdpr.SignalYes, req.Consent); err != nil || !allowSync { - req.Bidders = nil - return - } +type cookieSyncResponse struct { + Status string `json:"status"` + BidderStatus []cookieSyncResponseBidder `json:"bidder_status"` +} - for i := 0; i < len(req.Bidders); i++ { - if allowSync, err := permissions.BidderSyncAllowed(context.Background(), openrtb_ext.BidderName(req.Bidders[i]), gdpr.SignalYes, req.Consent); err != nil || !allowSync { - req.Bidders = append(req.Bidders[:i], req.Bidders[i+1:]...) - i-- - } - } +type cookieSyncResponseBidder struct { + BidderCode string `json:"bidder"` + NoCookie bool `json:"no_cookie,omitempty"` + UsersyncInfo cookieSyncResponseSync `json:"usersync,omitempty"` } -func (req *cookieSyncRequest) filterForCCPA(bidderMap map[string]struct{}) { - ccpaPolicy := &ccpa.Policy{Consent: req.USPrivacy} - ccpaParsedPolicy, err := ccpaPolicy.Parse(bidderMap) +type cookieSyncResponseSync struct { + URL string `json:"url,omitempty"` + Type string `json:"type,omitempty"` + SupportCORS bool `json:"supportCORS,omitempty"` +} - if err == nil { - for i := 0; i < len(req.Bidders); i++ { - if ccpaParsedPolicy.ShouldEnforce(req.Bidders[i]) { - req.Bidders = append(req.Bidders[:i], req.Bidders[i+1:]...) - i-- - } - } - } +type usersyncPrivacyConfig struct { + gdprConfig config.GDPR + gdprPermissions gdpr.Permissions + ccpaEnforce bool + bidderHashSet map[string]struct{} } -// filterToLimit will enforce a max limit on cookiesyncs supplied, picking a random subset of syncs to get to the limit if over. -func (req *cookieSyncRequest) filterToLimit() { - if req.Limit <= 0 { - return - } - if req.Limit >= len(req.Bidders) { - return - } +type usersyncPrivacy struct { + gdprPermissions gdpr.Permissions + gdprSignal gdpr.Signal + gdprConsent string + ccpaParsedPolicy ccpa.ParsedPolicy +} - // Modified Fisher and Yates' shuffle. We don't need the bidder list shuffled, so we stop shuffling once the final values beyond limit have been set. - // We also don't bother saving the values that should go into the entries beyond limit, as they will be discarded. - for i := len(req.Bidders) - 1; i >= req.Limit; i-- { - j := rand.Intn(i + 1) - if i != j { - req.Bidders[j] = req.Bidders[i] - // Don't complete the swap as the new value for req.Bidders[i] will be discarded below, and will never again be accessed as part of the swapping. - } - } - req.Bidders = req.Bidders[:req.Limit] - return +func (p usersyncPrivacy) GDPRAllowsHostCookie() bool { + allowCookie, err := p.gdprPermissions.HostCookiesAllowed(context.Background(), p.gdprSignal, p.gdprConsent) + return err == nil && allowCookie } -type cookieSyncResponse struct { - Status string `json:"status"` - BidderStatus []*usersync.CookieSyncBidders `json:"bidder_status"` +func (p usersyncPrivacy) GDPRAllowsBidderSync(bidder string) bool { + allowSync, err := p.gdprPermissions.BidderSyncAllowed(context.Background(), openrtb_ext.BidderName(bidder), p.gdprSignal, p.gdprConsent) + return err == nil && allowSync +} + +func (p usersyncPrivacy) CCPAAllowsBidderSync(bidder string) bool { + enforce := p.ccpaParsedPolicy.CanEnforce() && p.ccpaParsedPolicy.ShouldEnforce(bidder) + return !enforce } diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 2f56c262979..2ef2eb1770b 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -2,256 +2,1335 @@ package endpoints import ( "context" + "errors" + "io" "net/http" "net/http/httptest" "strings" "testing" - "text/template" "time" - "github.com/buger/jsonparser" - "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/adapters/appnexus" - "github.com/prebid/prebid-server/adapters/audienceNetwork" - "github.com/prebid/prebid-server/adapters/pubmatic" - analyticsConf "github.com/prebid/prebid-server/analytics/config" + "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/gdpr" - metricsConf "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" "github.com/prebid/prebid-server/usersync" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) -func TestCookieSyncNoCookies(t *testing.T) { - rr := doPost(`{"bidders":["appnexus", "audienceNetwork", "random"]}`, 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"}, parseSyncs(t, rr.Body.Bytes())) - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) +func TestNewCookieSyncEndpoint(t *testing.T) { + var ( + syncersByBidder = map[string]usersync.Syncer{"a": &MockSyncer{}} + gdprPerms = MockGDPRPerms{} + configUserSync = config.UserSync{Cooperative: config.UserSyncCooperative{EnabledByDefault: true}} + configHostCookie = config.HostCookie{Family: "foo"} + configGDPR = config.GDPR{HostVendorID: 42} + configCCPAEnforce = true + metrics = metrics.MetricsEngineMock{} + analytics = MockAnalytics{} + bidders = map[string]openrtb_ext.BidderName{"bidderA": openrtb_ext.BidderName("bidderA"), "bidderB": openrtb_ext.BidderName("bidderB")} + ) + + endpoint := NewCookieSyncEndpoint( + syncersByBidder, + &config.Configuration{ + UserSync: configUserSync, + HostCookie: configHostCookie, + GDPR: configGDPR, + CCPA: config.CCPA{Enforce: configCCPAEnforce}, + }, + &gdprPerms, + &metrics, + &analytics, + bidders, + ) + + expected := &cookieSyncEndpoint{ + chooser: usersync.NewChooser(syncersByBidder), + config: configUserSync, + hostCookieConfig: &configHostCookie, + privacyConfig: usersyncPrivacyConfig{ + gdprConfig: configGDPR, + gdprPermissions: &gdprPerms, + ccpaEnforce: configCCPAEnforce, + bidderHashSet: map[string]struct{}{"bidderA": {}, "bidderB": {}}, + }, + metrics: &metrics, + pbsAnalytics: &analytics, + } + + assert.Equal(t, expected, endpoint) +} + +// usersyncPrivacy +func TestCookieSyncHandle(t *testing.T) { + syncTypeExpected := []usersync.SyncType{usersync.SyncTypeIFrame, usersync.SyncTypeRedirect} + sync := usersync.Sync{URL: "aURL", Type: usersync.SyncTypeRedirect, SupportCORS: true} + syncer := MockSyncer{} + syncer.On("GetSync", syncTypeExpected, privacy.Policies{}).Return(sync, nil).Maybe() + + cookieWithSyncs := usersync.NewCookie() + cookieWithSyncs.TrySync("foo", "anyID") + + testCases := []struct { + description string + givenCookie *usersync.Cookie + givenBody io.Reader + givenChooserResult usersync.Result + expectedStatusCode int + expectedBody string + setMetricsExpectations func(*metrics.MetricsEngineMock) + setAnalyticsExpectations func(*MockAnalytics) + }{ + { + description: "Request With Cookie", + givenCookie: cookieWithSyncs, + givenBody: strings.NewReader(`{}`), + givenChooserResult: usersync.Result{ + Status: usersync.StatusOK, + BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, + SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, + }, + expectedStatusCode: 200, + expectedBody: `{"status":"ok","bidder_status":[` + + `{"bidder":"a","no_cookie":true,"usersync":{"url":"aURL","type":"redirect","supportCORS":true}}` + + `]}` + "\n", + setMetricsExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncOK).Once() + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() + }, + setAnalyticsExpectations: func(a *MockAnalytics) { + expected := analytics.CookieSyncObject{ + Status: 200, + Errors: nil, + BidderStatus: []*analytics.CookieSyncBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "redirect", SupportCORS: true}, + }, + }, + } + a.On("LogCookieSyncObject", &expected).Once() + }, + }, + { + description: "Request Without Cookie", + givenCookie: nil, + givenBody: strings.NewReader(`{}`), + givenChooserResult: usersync.Result{ + Status: usersync.StatusOK, + BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, + SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, + }, + expectedStatusCode: 200, + expectedBody: `{"status":"no_cookie","bidder_status":[` + + `{"bidder":"a","no_cookie":true,"usersync":{"url":"aURL","type":"redirect","supportCORS":true}}` + + `]}` + "\n", + setMetricsExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncOK).Once() + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() + }, + setAnalyticsExpectations: func(a *MockAnalytics) { + expected := analytics.CookieSyncObject{ + Status: 200, + Errors: nil, + BidderStatus: []*analytics.CookieSyncBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "redirect", SupportCORS: true}, + }, + }, + } + a.On("LogCookieSyncObject", &expected).Once() + }, + }, + { + description: "Malformed Request", + givenCookie: cookieWithSyncs, + givenBody: strings.NewReader(`malformed`), + givenChooserResult: usersync.Result{ + Status: usersync.StatusOK, + BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, + SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, + }, + expectedStatusCode: 400, + expectedBody: `JSON parsing failed: invalid character 'm' looking for beginning of value` + "\n", + setMetricsExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncBadRequest).Once() + }, + setAnalyticsExpectations: func(a *MockAnalytics) { + expected := analytics.CookieSyncObject{ + Status: 400, + Errors: []error{errors.New("JSON parsing failed: invalid character 'm' looking for beginning of value")}, + BidderStatus: []*analytics.CookieSyncBidder{}, + } + a.On("LogCookieSyncObject", &expected).Once() + }, + }, + { + description: "Request Blocked By Opt Out", + givenCookie: cookieWithSyncs, + givenBody: strings.NewReader(`{}`), + givenChooserResult: usersync.Result{ + Status: usersync.StatusBlockedByUserOptOut, + BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, + SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, + }, + expectedStatusCode: 401, + expectedBody: `User has opted out` + "\n", + setMetricsExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncOptOut).Once() + }, + setAnalyticsExpectations: func(a *MockAnalytics) { + expected := analytics.CookieSyncObject{ + Status: 401, + Errors: []error{errors.New("User has opted out")}, + BidderStatus: []*analytics.CookieSyncBidder{}, + } + a.On("LogCookieSyncObject", &expected).Once() + }, + }, + { + description: "Request Blocked By GDPR Host Cookie Restriction", + givenCookie: cookieWithSyncs, + givenBody: strings.NewReader(`{}`), + givenChooserResult: usersync.Result{ + Status: usersync.StatusBlockedByGDPR, + BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, + SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, + }, + expectedStatusCode: 200, + expectedBody: `{"status":"ok","bidder_status":[]}` + "\n", + setMetricsExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordCookieSync", metrics.CookieSyncGDPRHostCookieBlocked).Once() + }, + setAnalyticsExpectations: func(a *MockAnalytics) { + expected := analytics.CookieSyncObject{ + Status: 200, + Errors: nil, + BidderStatus: []*analytics.CookieSyncBidder{}, + } + a.On("LogCookieSyncObject", &expected).Once() + }, + }, + } + + for _, test := range testCases { + mockMetrics := metrics.MetricsEngineMock{} + test.setMetricsExpectations(&mockMetrics) + + mockAnalytics := MockAnalytics{} + test.setAnalyticsExpectations(&mockAnalytics) + + request := httptest.NewRequest("POST", "/cookiesync", test.givenBody) + if test.givenCookie != nil { + request.AddCookie(test.givenCookie.ToHTTPCookie(24 * time.Hour)) + } + + writer := httptest.NewRecorder() + + endpoint := cookieSyncEndpoint{ + chooser: FakeChooser{Result: test.givenChooserResult}, + hostCookieConfig: &config.HostCookie{}, + privacyConfig: usersyncPrivacyConfig{ + gdprConfig: config.GDPR{ + Enabled: true, + DefaultValue: "0", + }, + ccpaEnforce: true, + }, + metrics: &mockMetrics, + pbsAnalytics: &mockAnalytics, + } + endpoint.Handle(writer, request, nil) + + assert.Equal(t, test.expectedStatusCode, writer.Code, test.description+":status_code") + assert.Equal(t, test.expectedBody, writer.Body.String(), test.description+":body") + mockMetrics.AssertExpectations(t) + mockAnalytics.AssertExpectations(t) + } } -func TestGDPRPreventsCookie(t *testing.T) { - rr := doPost(`{"bidders":["appnexus", "pubmatic"]}`, nil, false, syncersForTest()) - assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") - assert.Equal(t, http.StatusOK, rr.Code) - assert.Empty(t, parseSyncs(t, rr.Body.Bytes())) - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) +func TestCookieSyncParseRequest(t *testing.T) { + expectedCCPAParsedPolicy, _ := ccpa.Policy{Consent: "1NYN"}.Parse(map[string]struct{}{}) + + testCases := []struct { + description string + givenConfig config.UserSync + givenBody io.Reader + givenGDPRConfig config.GDPR + givenCCPAEnabled bool + expectedError string + expectedPrivacy privacy.Policies + expectedRequest usersync.Request + }{ + { + description: "Complete Request", + givenBody: strings.NewReader(`{` + + `"bidders":["a", "b"],` + + `"gdpr":"1",` + + `"gdpr_consent":"anyGDPRConsent",` + + `"us_privacy":"1NYN",` + + `"limit":42,` + + `"coopSync":true,` + + `"filterSettings":{"iframe":{"bidders":"*","filter":"include"}, "image":{"bidders":["b"],"filter":"exclude"}}` + + `}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: false, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + }, + expectedPrivacy: privacy.Policies{ + GDPR: gdprPrivacy.Policy{ + Signal: "1", + Consent: "anyGDPRConsent", + }, + CCPA: ccpa.Policy{ + Consent: "1NYN", + }, + }, + expectedRequest: usersync.Request{ + Bidders: []string{"a", "b"}, + Cooperative: usersync.Cooperative{ + Enabled: true, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + Limit: 42, + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalYes, + gdprConsent: "anyGDPRConsent", + ccpaParsedPolicy: expectedCCPAParsedPolicy, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewSpecificBidderFilter([]string{"b"}, usersync.BidderFilterModeExclude), + }, + }, + }, + { + description: "Complete Request - Legacy Fields Only", + givenBody: strings.NewReader(`{` + + `"bidders":["a", "b"],` + + `"gdpr":"1",` + + `"gdpr_consent":"anyGDPRConsent",` + + `"us_privacy":"1NYN",` + + `"limit":42` + + `}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: false, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + }, + expectedPrivacy: privacy.Policies{ + GDPR: gdprPrivacy.Policy{ + Signal: "1", + Consent: "anyGDPRConsent", + }, + CCPA: ccpa.Policy{ + Consent: "1NYN", + }, + }, + expectedRequest: usersync.Request{ + Bidders: []string{"a", "b"}, + Cooperative: usersync.Cooperative{ + Enabled: false, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + Limit: 42, + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalYes, + gdprConsent: "anyGDPRConsent", + ccpaParsedPolicy: expectedCCPAParsedPolicy, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Empty Request", + givenBody: strings.NewReader(`{}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + expectedPrivacy: privacy.Policies{}, + expectedRequest: usersync.Request{ + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Cooperative Unspecified - Default True", + givenBody: strings.NewReader(`{}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: true, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + }, + expectedPrivacy: privacy.Policies{}, + expectedRequest: usersync.Request{ + Cooperative: usersync.Cooperative{ + Enabled: true, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Cooperative Unspecified - Default False", + givenBody: strings.NewReader(`{}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: false, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + }, + expectedPrivacy: privacy.Policies{}, + expectedRequest: usersync.Request{ + Cooperative: usersync.Cooperative{ + Enabled: false, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Cooperative False - Default True", + givenBody: strings.NewReader(`{"coopSync":false}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: true, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + }, + expectedPrivacy: privacy.Policies{}, + expectedRequest: usersync.Request{ + Cooperative: usersync.Cooperative{ + Enabled: false, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Cooperative False - Default False", + givenBody: strings.NewReader(`{"coopSync":false}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: false, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + }, + expectedPrivacy: privacy.Policies{}, + expectedRequest: usersync.Request{ + Cooperative: usersync.Cooperative{ + Enabled: false, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Cooperative True - Default True", + givenBody: strings.NewReader(`{"coopSync":true}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: true, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + }, + expectedPrivacy: privacy.Policies{}, + expectedRequest: usersync.Request{ + Cooperative: usersync.Cooperative{ + Enabled: true, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Cooperative True - Default False", + givenBody: strings.NewReader(`{"coopSync":true}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: false, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + }, + expectedPrivacy: privacy.Policies{}, + expectedRequest: usersync.Request{ + Cooperative: usersync.Cooperative{ + Enabled: true, + PriorityGroups: [][]string{{"a", "b", "c"}}, + }, + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "CCPA Consent Invalid", + givenBody: strings.NewReader(`{"us_privacy":"invalid"}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + expectedPrivacy: privacy.Policies{}, + expectedRequest: usersync.Request{ + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "CCPA Disabled", + givenBody: strings.NewReader(`{"us_privacy":"1NYN"}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: false, + expectedPrivacy: privacy.Policies{ + CCPA: ccpa.Policy{ + Consent: "1NYN"}, + }, + expectedRequest: usersync.Request{ + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Invalid JSON", + givenBody: strings.NewReader(`malformed`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + expectedError: "JSON parsing failed: invalid character 'm' looking for beginning of value", + }, + { + description: "Invalid Type Filter", + givenBody: strings.NewReader(`{"filterSettings":{"iframe":{"bidders":"invalid","filter":"exclude"}}}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + expectedError: "error parsing filtersettings.iframe: invalid bidders value `invalid`. must either be '*' or a string array", + }, + { + description: "Invalid GDPR Signal", + givenBody: strings.NewReader(`{"gdpr":"invalid"}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + expectedError: "GDPR signal should be integer 0 or 1", + }, + { + description: "Missing GDPR Consent - Explicit Signal 0", + givenBody: strings.NewReader(`{"gdpr":"0"}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + expectedPrivacy: privacy.Policies{ + GDPR: gdprPrivacy.Policy{Signal: "0"}, + }, + expectedRequest: usersync.Request{ + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalNo, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Missing GDPR Consent - Explicit Signal 1", + givenBody: strings.NewReader(`{"gdpr":"1"}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + expectedError: "gdpr_consent is required if gdpr=1", + }, + { + description: "Missing GDPR Consent - Ambiguous Signal - Default Value 0", + givenBody: strings.NewReader(`{}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + expectedPrivacy: privacy.Policies{ + GDPR: gdprPrivacy.Policy{Signal: ""}, + }, + expectedRequest: usersync.Request{ + Privacy: usersyncPrivacy{ + gdprSignal: gdpr.SignalAmbiguous, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "Missing GDPR Consent - Ambiguous Signal - Default Value 1", + givenBody: strings.NewReader(`{}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "1"}, + givenCCPAEnabled: true, + expectedError: "gdpr_consent is required. gdpr is not specified and is assumed to be 1 by the server. set gdpr=0 to exempt this request", + }, + { + description: "HTTP Read Error", + givenBody: ErrReader(errors.New("anyError")), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + expectedError: "Failed to read request body", + }, + } + + for _, test := range testCases { + httpRequest := httptest.NewRequest("POST", "/cookiesync", test.givenBody) + + endpoint := cookieSyncEndpoint{ + config: test.givenConfig, + privacyConfig: usersyncPrivacyConfig{ + gdprConfig: test.givenGDPRConfig, + ccpaEnforce: test.givenCCPAEnabled, + }, + } + request, privacyPolicies, err := endpoint.parseRequest(httpRequest) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + assert.Equal(t, test.expectedRequest, request, test.description+":request") + assert.Equal(t, test.expectedPrivacy, privacyPolicies, test.description+":privacy") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + assert.Empty(t, request, test.description+":request") + assert.Empty(t, privacyPolicies, test.description+":privacy") + } + } +} + +func TestParseTypeFilter(t *testing.T) { + testCases := []struct { + description string + given *cookieSyncRequestFilterSettings + expectedError string + expectedFilter usersync.SyncTypeFilter + }{ + { + description: "Nil", + given: nil, + expectedFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + { + description: "Nil Object", + given: &cookieSyncRequestFilterSettings{}, + expectedFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + { + description: "Given IFrame Only", + given: &cookieSyncRequestFilterSettings{ + IFrame: &cookieSyncRequestFilter{Bidders: []interface{}{"a"}, Mode: "exclude"}, + }, + expectedFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewSpecificBidderFilter([]string{"a"}, usersync.BidderFilterModeExclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + { + description: "Given Redirect Only", + given: &cookieSyncRequestFilterSettings{ + Redirect: &cookieSyncRequestFilter{Bidders: []interface{}{"b"}, Mode: "exclude"}, + }, + expectedFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewSpecificBidderFilter([]string{"b"}, usersync.BidderFilterModeExclude), + }, + }, + { + description: "Given Both", + given: &cookieSyncRequestFilterSettings{ + IFrame: &cookieSyncRequestFilter{Bidders: []interface{}{"a"}, Mode: "exclude"}, + Redirect: &cookieSyncRequestFilter{Bidders: []interface{}{"b"}, Mode: "exclude"}, + }, + expectedFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewSpecificBidderFilter([]string{"a"}, usersync.BidderFilterModeExclude), + Redirect: usersync.NewSpecificBidderFilter([]string{"b"}, usersync.BidderFilterModeExclude), + }, + }, + { + description: "IFrame Error", + given: &cookieSyncRequestFilterSettings{ + IFrame: &cookieSyncRequestFilter{Bidders: 42, Mode: "exclude"}, + Redirect: &cookieSyncRequestFilter{Bidders: []interface{}{"b"}, Mode: "exclude"}, + }, + expectedError: "error parsing filtersettings.iframe: invalid bidders type. must either be a string '*' or a string array of bidders", + }, + { + description: "Redirect Error", + given: &cookieSyncRequestFilterSettings{ + IFrame: &cookieSyncRequestFilter{Bidders: []interface{}{"a"}, Mode: "exclude"}, + Redirect: &cookieSyncRequestFilter{Bidders: 42, Mode: "exclude"}, + }, + expectedError: "error parsing filtersettings.image: invalid bidders type. must either be a string '*' or a string array of bidders", + }, + } + + for _, test := range testCases { + result, err := parseTypeFilter(test.given) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + assert.Equal(t, test.expectedFilter, result, test.description+":result") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + assert.Empty(t, result, test.description+":result") + } + } +} + +func TestParseBidderFilter(t *testing.T) { + testCases := []struct { + description string + given *cookieSyncRequestFilter + expectedError string + expectedFilter usersync.BidderFilter + }{ + { + description: "Nil", + given: nil, + expectedFilter: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + { + description: "All Bidders - Include", + given: &cookieSyncRequestFilter{Bidders: "*", Mode: "include"}, + expectedFilter: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + { + description: "All Bidders - Exclude", + given: &cookieSyncRequestFilter{Bidders: "*", Mode: "exclude"}, + expectedFilter: usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude), + }, + { + description: "All Bidders - Invalid Mode", + given: &cookieSyncRequestFilter{Bidders: "*", Mode: "invalid"}, + expectedError: "invalid filter value 'invalid'. must be either 'include' or 'exclude'", + }, + { + description: "All Bidders - Unexpected Bidders Value", + given: &cookieSyncRequestFilter{Bidders: "invalid", Mode: "include"}, + expectedError: "invalid bidders value `invalid`. must either be '*' or a string array", + }, + { + description: "Specific Bidders - Include", + given: &cookieSyncRequestFilter{Bidders: []interface{}{"a", "b"}, Mode: "include"}, + expectedFilter: usersync.NewSpecificBidderFilter([]string{"a", "b"}, usersync.BidderFilterModeInclude), + }, + { + description: "Specific Bidders - Exclude", + given: &cookieSyncRequestFilter{Bidders: []interface{}{"a", "b"}, Mode: "exclude"}, + expectedFilter: usersync.NewSpecificBidderFilter([]string{"a", "b"}, usersync.BidderFilterModeExclude), + }, + { + description: "Specific Bidders - Invalid Mode", + given: &cookieSyncRequestFilter{Bidders: []interface{}{"a", "b"}, Mode: "invalid"}, + expectedError: "invalid filter value 'invalid'. must be either 'include' or 'exclude'", + }, + { + description: "Invalid Bidders Type", + given: &cookieSyncRequestFilter{Bidders: 42, Mode: "include"}, + expectedError: "invalid bidders type. must either be a string '*' or a string array of bidders", + }, + { + description: "Invalid Bidders Type Of Array Element", + given: &cookieSyncRequestFilter{Bidders: []interface{}{"a", 42}, Mode: "include"}, + expectedError: "invalid bidders type. must either be a string '*' or a string array of bidders", + }, + } + + for _, test := range testCases { + result, err := parseBidderFilter(test.given) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + assert.Equal(t, test.expectedFilter, result, test.description+":result") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + assert.Nil(t, result, test.description+":result") + } + } } -func TestGDPRPreventsBidders(t *testing.T) { - 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"))), +func TestCookieSyncHandleError(t *testing.T) { + err := errors.New("anyError") + + mockAnalytics := MockAnalytics{} + mockAnalytics.On("LogCookieSyncObject", mock.Anything) + writer := httptest.NewRecorder() + + endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics} + endpoint.handleError(writer, err, 418) + + assert.Equal(t, writer.Code, 418) + assert.Equal(t, writer.Body.String(), "anyError\n") + mockAnalytics.AssertCalled(t, "LogCookieSyncObject", &analytics.CookieSyncObject{ + Status: 418, + Errors: []error{err}, + BidderStatus: []*analytics.CookieSyncBidder{}, }) - assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") - assert.Equal(t, http.StatusOK, rr.Code) - assert.ElementsMatch(t, []string{"pubmatic"}, parseSyncs(t, rr.Body.Bytes())) - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) } -func TestGDPRIgnoredIfZero(t *testing.T) { - rr := doPost(`{"gdpr":0,"bidders":["appnexus", "pubmatic"]}`, nil, false, nil) - 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", "pubmatic"}, parseSyncs(t, rr.Body.Bytes())) - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) +func TestCookieSyncWriteBidderMetrics(t *testing.T) { + testCases := []struct { + description string + given []usersync.BidderEvaluation + setExpectations func(*metrics.MetricsEngineMock) + }{ + { + description: "None", + given: []usersync.BidderEvaluation{}, + setExpectations: func(m *metrics.MetricsEngineMock) { + }, + }, + { + description: "One - OK", + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, + setExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() + }, + }, + { + description: "One - Blocked By GDPR", + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByGDPR}}, + setExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() + }, + }, + { + description: "One - Blocked By CCPA", + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByCCPA}}, + setExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() + }, + }, + { + description: "One - Already Synced", + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusAlreadySynced}}, + setExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncAlreadySynced).Once() + }, + }, + { + description: "One - Type Not Supported", + given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusTypeNotSupported}}, + setExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncTypeNotSupported).Once() + }, + }, + { + description: "Many", + given: []usersync.BidderEvaluation{ + {Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}, + {Bidder: "b", SyncerKey: "bSyncer", Status: usersync.StatusAlreadySynced}, + }, + setExpectations: func(m *metrics.MetricsEngineMock) { + m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() + m.On("RecordSyncerRequest", "bSyncer", metrics.SyncerCookieSyncAlreadySynced).Once() + }, + }, + } + + for _, test := range testCases { + mockMetrics := metrics.MetricsEngineMock{} + test.setExpectations(&mockMetrics) + + endpoint := &cookieSyncEndpoint{metrics: &mockMetrics} + endpoint.writeBidderMetrics(test.given) + + mockMetrics.AssertExpectations(t) + } } -func TestGDPRConsentRequired(t *testing.T) { - rr := doPost(`{"gdpr":1,"bidders":["appnexus", "pubmatic"]}`, nil, false, nil) - assert.Equal(t, rr.Header().Get("Content-Type"), "text/plain; charset=utf-8") - assert.Equal(t, http.StatusBadRequest, rr.Code) - assert.Equal(t, "gdpr_consent is required if gdpr=1\n", rr.Body.String()) +func TestCookieSyncHandleResponse(t *testing.T) { + syncTypeFilter := usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + } + syncTypeExpected := []usersync.SyncType{usersync.SyncTypeRedirect} + privacyPolicies := privacy.Policies{CCPA: ccpa.Policy{Consent: "anyConsent"}} + + // The & in the URL is necessary to test proper JSON encoding. + syncA := usersync.Sync{URL: "https://syncA.com/sync?a=1&b=2", Type: usersync.SyncTypeRedirect, SupportCORS: true} + syncerA := MockSyncer{} + syncerA.On("GetSync", syncTypeExpected, privacyPolicies).Return(syncA, nil).Maybe() + + // The & in the URL is necessary to test proper JSON encoding. + syncB := usersync.Sync{URL: "https://syncB.com/sync?a=1&b=2", Type: usersync.SyncTypeRedirect, SupportCORS: false} + syncerB := MockSyncer{} + syncerB.On("GetSync", syncTypeExpected, privacyPolicies).Return(syncB, nil).Maybe() + + syncWithError := usersync.Sync{} + syncerWithError := MockSyncer{} + syncerWithError.On("GetSync", syncTypeExpected, privacyPolicies).Return(syncWithError, errors.New("anyError")).Maybe() + + testCases := []struct { + description string + givenCookieHasSyncs bool + givenSyncersChosen []usersync.SyncerChoice + expectedJSON string + expectedAnalytics analytics.CookieSyncObject + }{ + { + description: "None", + givenCookieHasSyncs: true, + givenSyncersChosen: []usersync.SyncerChoice{}, + expectedJSON: `{"status":"ok","bidder_status":[]}` + "\n", + expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, + }, + { + description: "One", + givenCookieHasSyncs: true, + givenSyncersChosen: []usersync.SyncerChoice{{Bidder: "foo", Syncer: &syncerA}}, + expectedJSON: `{"status":"ok","bidder_status":[` + + `{"bidder":"foo","no_cookie":true,"usersync":{"url":"https://syncA.com/sync?a=1&b=2","type":"redirect","supportCORS":true}}` + + `]}` + "\n", + expectedAnalytics: analytics.CookieSyncObject{ + Status: 200, + BidderStatus: []*analytics.CookieSyncBidder{ + { + BidderCode: "foo", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "https://syncA.com/sync?a=1&b=2", Type: "redirect", SupportCORS: true}, + }, + }, + }, + }, + { + description: "Many", + givenCookieHasSyncs: true, + givenSyncersChosen: []usersync.SyncerChoice{{Bidder: "foo", Syncer: &syncerA}, {Bidder: "bar", Syncer: &syncerB}}, + expectedJSON: `{"status":"ok","bidder_status":[` + + `{"bidder":"foo","no_cookie":true,"usersync":{"url":"https://syncA.com/sync?a=1&b=2","type":"redirect","supportCORS":true}},` + + `{"bidder":"bar","no_cookie":true,"usersync":{"url":"https://syncB.com/sync?a=1&b=2","type":"redirect"}}` + + `]}` + "\n", + expectedAnalytics: analytics.CookieSyncObject{ + Status: 200, + BidderStatus: []*analytics.CookieSyncBidder{ + { + BidderCode: "foo", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "https://syncA.com/sync?a=1&b=2", Type: "redirect", SupportCORS: true}, + }, + { + BidderCode: "bar", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "https://syncB.com/sync?a=1&b=2", Type: "redirect", SupportCORS: false}, + }, + }, + }, + }, + { + description: "Many With One GetSync Error", + givenCookieHasSyncs: true, + givenSyncersChosen: []usersync.SyncerChoice{{Bidder: "foo", Syncer: &syncerWithError}, {Bidder: "bar", Syncer: &syncerB}}, + expectedJSON: `{"status":"ok","bidder_status":[` + + `{"bidder":"bar","no_cookie":true,"usersync":{"url":"https://syncB.com/sync?a=1&b=2","type":"redirect"}}` + + `]}` + "\n", + expectedAnalytics: analytics.CookieSyncObject{ + Status: 200, + BidderStatus: []*analytics.CookieSyncBidder{ + { + BidderCode: "bar", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "https://syncB.com/sync?a=1&b=2", Type: "redirect", SupportCORS: false}, + }, + }, + }, + }, + { + description: "No Existing Syncs", + givenCookieHasSyncs: false, + givenSyncersChosen: []usersync.SyncerChoice{}, + expectedJSON: `{"status":"no_cookie","bidder_status":[]}` + "\n", + expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, + }, + } + + for _, test := range testCases { + mockAnalytics := MockAnalytics{} + mockAnalytics.On("LogCookieSyncObject", &test.expectedAnalytics).Once() + + cookie := usersync.NewCookie() + if test.givenCookieHasSyncs { + if err := cookie.TrySync("foo", "anyID"); err != nil { + assert.FailNow(t, test.description+":set_cookie") + } + } + + writer := httptest.NewRecorder() + endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics} + endpoint.handleResponse(writer, syncTypeFilter, cookie, privacyPolicies, test.givenSyncersChosen) + + if assert.Equal(t, writer.Code, http.StatusOK, test.description+":http_status") { + assert.Equal(t, writer.Header().Get("Content-Type"), "application/json; charset=utf-8", test.description+":http_header") + assert.Equal(t, test.expectedJSON, writer.Body.String(), test.description+":http_response") + } + mockAnalytics.AssertExpectations(t) + } } -func TestCCPA(t *testing.T) { +func TestMapBidderStatusToAnalytics(t *testing.T) { + testCases := []struct { + description string + given []cookieSyncResponseBidder + expected []*analytics.CookieSyncBidder + }{ + { + description: "None", + given: []cookieSyncResponseBidder{}, + expected: []*analytics.CookieSyncBidder{}, + }, + { + description: "One", + given: []cookieSyncResponseBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: cookieSyncResponseSync{URL: "aURL", Type: "aType", SupportCORS: false}, + }, + }, + expected: []*analytics.CookieSyncBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "aType", SupportCORS: false}, + }, + }, + }, + { + description: "Many", + given: []cookieSyncResponseBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: cookieSyncResponseSync{URL: "aURL", Type: "aType", SupportCORS: false}, + }, + { + BidderCode: "b", + NoCookie: false, + UsersyncInfo: cookieSyncResponseSync{URL: "bURL", Type: "bType", SupportCORS: true}, + }, + }, + expected: []*analytics.CookieSyncBidder{ + { + BidderCode: "a", + NoCookie: true, + UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "aType", SupportCORS: false}, + }, + { + BidderCode: "b", + NoCookie: false, + UsersyncInfo: &analytics.UsersyncInfo{URL: "bURL", Type: "bType", SupportCORS: true}, + }, + }, + }, + } + + for _, test := range testCases { + result := mapBidderStatusToAnalytics(test.given) + assert.ElementsMatch(t, test.expected, result, test.description) + } +} + +func TestUsersyncPrivacyGDPRAllowsHostCookie(t *testing.T) { testCases := []struct { description string - requestBody string - enforceCCPA bool - expectedSyncs []string + givenResponse bool + givenError error + expected bool }{ { - description: "Feature Flag On & Opt-Out Yes", - requestBody: `{"bidders":["appnexus"], "us_privacy":"1-Y-"}`, - enforceCCPA: true, - expectedSyncs: []string{}, + description: "Allowed - No Error", + givenResponse: true, + givenError: nil, + expected: true, + }, + { + description: "Allowed - Error", + givenResponse: true, + givenError: errors.New("anyError"), + expected: false, }, { - description: "Feature Flag Off & Opt-Out Yes", - requestBody: `{"bidders":["appnexus"], "us_privacy":"1-Y-"}`, - enforceCCPA: false, - expectedSyncs: []string{"appnexus"}, + description: "Not Allowed - No Error", + givenResponse: false, + givenError: nil, + expected: false, }, { - description: "Feature Flag On & Opt-Out No", - requestBody: `{"bidders":["appnexus"], "us_privacy":"1-N-"}`, - enforceCCPA: false, - expectedSyncs: []string{"appnexus"}, + description: "Not Allowed - Error", + givenResponse: false, + givenError: errors.New("anyError"), + expected: false, }, + } + + for _, test := range testCases { + mockPerms := MockGDPRPerms{} + mockPerms.On("HostCookiesAllowed", mock.Anything, gdpr.SignalYes, "anyConsent").Return(test.givenResponse, test.givenError) + + privacy := usersyncPrivacy{ + gdprPermissions: &mockPerms, + gdprSignal: gdpr.SignalYes, + gdprConsent: "anyConsent", + } + + result := privacy.GDPRAllowsHostCookie() + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestUsersyncPrivacyGDPRAllowsBidderSync(t *testing.T) { + testCases := []struct { + description string + givenResponse bool + givenError error + expected bool + }{ { - description: "Feature Flag On & Opt-Out Unknown", - requestBody: `{"bidders":["appnexus"], "us_privacy":"1---"}`, - enforceCCPA: false, - expectedSyncs: []string{"appnexus"}, + description: "Allowed - No Error", + givenResponse: true, + givenError: nil, + expected: true, }, { - description: "Feature Flag On & Opt-Out Invalid", - requestBody: `{"bidders":["appnexus"], "us_privacy":"invalid"}`, - enforceCCPA: false, - expectedSyncs: []string{"appnexus"}, + description: "Allowed - Error", + givenResponse: true, + givenError: errors.New("anyError"), + expected: false, }, { - description: "Feature Flag On & Opt-Out Not Provided", - requestBody: `{"bidders":["appnexus"]}`, - enforceCCPA: false, - expectedSyncs: []string{"appnexus"}, + description: "Not Allowed - No Error", + givenResponse: false, + givenError: nil, + expected: false, + }, + { + description: "Not Allowed - Error", + givenResponse: false, + givenError: errors.New("anyError"), + expected: false, }, } for _, test := range testCases { - 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") - assert.ElementsMatch(t, test.expectedSyncs, parseSyncs(t, rr.Body.Bytes()), test.description+":syncs") - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes()), test.description+":status") + mockPerms := MockGDPRPerms{} + mockPerms.On("BidderSyncAllowed", mock.Anything, openrtb_ext.BidderName("foo"), gdpr.SignalYes, "anyConsent").Return(test.givenResponse, test.givenError) + + privacy := usersyncPrivacy{ + gdprPermissions: &mockPerms, + gdprSignal: gdpr.SignalYes, + gdprConsent: "anyConsent", + } + + result := privacy.GDPRAllowsBidderSync("foo") + assert.Equal(t, test.expected, result, test.description) } } -func TestCookieSyncHasCookies(t *testing.T) { - rr := doPost(`{"bidders":["appnexus", "audienceNetwork", "random"]}`, map[string]string{ - "adnxs": "1234", - "audienceNetwork": "2345", - }, true, syncersForTest()) - assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") - assert.Equal(t, http.StatusOK, rr.Code) - assert.Empty(t, parseSyncs(t, rr.Body.Bytes())) - assert.Equal(t, "ok", parseStatus(t, rr.Body.Bytes())) +func TestUsersyncPrivacyCCPAAllowsBidderSync(t *testing.T) { + testCases := []struct { + description string + givenConsent string + expected bool + }{ + { + description: "Allowed - No Opt-Out", + givenConsent: "1NNN", + expected: true, + }, + { + description: "Not Allowed - Opt-Out", + givenConsent: "1NYN", + expected: false, + }, + { + description: "Not Specified", + givenConsent: "", + expected: true, + }, + } + + for _, test := range testCases { + validBidders := map[string]struct{}{"foo": {}} + parsedPolicy, err := ccpa.Policy{Consent: test.givenConsent}.Parse(validBidders) + + if assert.NoError(t, err) { + privacy := usersyncPrivacy{ccpaParsedPolicy: parsedPolicy} + result := privacy.CCPAAllowsBidderSync("foo") + assert.Equal(t, test.expected, result, test.description) + } + } } -// Make sure that an empty bidders array returns no syncs -func TestCookieSyncEmptyBidders(t *testing.T) { - rr := doPost(`{"bidders": []}`, nil, true, syncersForTest()) - assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") - assert.Equal(t, http.StatusOK, rr.Code) - assert.Empty(t, parseSyncs(t, rr.Body.Bytes())) - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) +type FakeChooser struct { + Result usersync.Result } -// Make sure that all syncs are returned if "bidders" isn't a key -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", "pubmatic"}, parseSyncs(t, rr.Body.Bytes())) - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) +func (c FakeChooser) Choose(request usersync.Request, cookie *usersync.Cookie) usersync.Result { + return c.Result } -func TestCookieSyncNoCookiesBrokenGDPR(t *testing.T) { - 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())) - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) +type MockSyncer struct { + mock.Mock } -func TestCookieSyncWithLimit(t *testing.T) { - rr := doPost(`{"limit":2}`, nil, true, syncersForTest()) - assert.Equal(t, http.StatusOK, rr.Code) - assert.Len(t, parseSyncs(t, rr.Body.Bytes()), 2, "usersyncs") - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) +func (m *MockSyncer) Key() string { + args := m.Called() + return args.String(0) } -func TestCookieSyncWithLargeLimit(t *testing.T) { - syncers := syncersForTest() - rr := doPost(`{"limit":1000}`, nil, true, syncers) - assert.Equal(t, http.StatusOK, rr.Code) - assert.Len(t, parseSyncs(t, rr.Body.Bytes()), len(syncers), "usersyncs") - assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) +func (m *MockSyncer) DefaultSyncType() usersync.SyncType { + args := m.Called() + return args.Get(0).(usersync.SyncType) } -func doPost(body string, existingSyncs map[string]string, gdprHostConsent bool, gdprBidders map[openrtb_ext.BidderName]usersync.Usersyncer) *httptest.ResponseRecorder { - return doConfigurablePost(body, existingSyncs, gdprHostConsent, gdprBidders, config.GDPR{}, config.CCPA{}) +func (m *MockSyncer) SupportsType(syncTypes []usersync.SyncType) bool { + args := m.Called(syncTypes) + return args.Bool(0) } -func doConfigurablePost(body string, existingSyncs map[string]string, gdprHostConsent bool, gdprBidders map[openrtb_ext.BidderName]usersync.Usersyncer, cfgGDPR config.GDPR, cfgCCPA config.CCPA) *httptest.ResponseRecorder { - endpoint := testableEndpoint(mockPermissions(gdprHostConsent, gdprBidders), cfgGDPR, cfgCCPA) - router := httprouter.New() - router.POST("/cookie_sync", endpoint) - req, _ := http.NewRequest("POST", "/cookie_sync", strings.NewReader(body)) - if len(existingSyncs) > 0 { +func (m *MockSyncer) GetSync(syncTypes []usersync.SyncType, privacyPolicies privacy.Policies) (usersync.Sync, error) { + args := m.Called(syncTypes, privacyPolicies) + return args.Get(0).(usersync.Sync), args.Error(1) +} - pcs := usersync.NewPBSCookie() - for bidder, uid := range existingSyncs { - pcs.TrySync(bidder, uid) - } - req.AddCookie(pcs.ToHTTPCookie(90 * 24 * time.Hour)) - } +type MockAnalytics struct { + mock.Mock +} - rr := httptest.NewRecorder() - endpoint(rr, req, nil) - return rr +func (m *MockAnalytics) LogAuctionObject(obj *analytics.AuctionObject) { + m.Called(obj) } -func testableEndpoint(perms gdpr.Permissions, cfgGDPR config.GDPR, cfgCCPA config.CCPA) httprouter.Handle { - return NewCookieSyncEndpoint(syncersForTest(), &config.Configuration{GDPR: cfgGDPR, CCPA: cfgCCPA}, perms, &metricsConf.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), openrtb_ext.BuildBidderMap()) +func (m *MockAnalytics) LogVideoObject(obj *analytics.VideoObject) { + m.Called(obj) } -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.BidderPubmatic: pubmatic.NewPubmaticSyncer(template.Must(template.New("sync").Parse("thaturl.com"))), - } +func (m *MockAnalytics) LogCookieSyncObject(obj *analytics.CookieSyncObject) { + m.Called(obj) } -func parseStatus(t *testing.T, responseBody []byte) string { - t.Helper() - val, err := jsonparser.GetString(responseBody, "status") - if err != nil { - t.Fatalf("response.status was not a string. Error was %v", err) - } - return val +func (m *MockAnalytics) LogSetUIDObject(obj *analytics.SetUIDObject) { + m.Called(obj) } -func parseSyncs(t *testing.T, response []byte) []string { - t.Helper() - var syncs []string - jsonparser.ArrayEach(response, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { - if dataType != jsonparser.Object { - t.Errorf("response.bidder_status contained unexpected element of type %v.", dataType) - } - if val, err := jsonparser.GetString(value, "bidder"); err != nil { - t.Errorf("response.bidder_status[?].bidder was not a string. Value was %s", string(value)) - } else { - syncs = append(syncs, val) - } - }, "bidder_status") - return syncs +func (m *MockAnalytics) LogAmpObject(obj *analytics.AmpObject) { + m.Called(obj) } -func mockPermissions(allowHost bool, allowedBidders map[openrtb_ext.BidderName]usersync.Usersyncer) gdpr.Permissions { - return &gdprPerms{ - allowHost: allowHost, - allowedBidders: allowedBidders, - } +func (m *MockAnalytics) LogNotificationEventObject(obj *analytics.NotificationEvent) { + m.Called(obj) +} + +type MockGDPRPerms struct { + mock.Mock +} + +func (m *MockGDPRPerms) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { + args := m.Called(ctx, gdprSignal, consent) + return args.Bool(0), args.Error(1) +} + +func (m *MockGDPRPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { + args := m.Called(ctx, bidder, gdprSignal, consent) + return args.Bool(0), args.Error(1) } -type gdprPerms struct { - allowHost bool - allowedBidders map[openrtb_ext.BidderName]usersync.Usersyncer +func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidReq bool, passGeo bool, passID bool, err error) { + args := m.Called(ctx, bidder, PublisherID, gdprSignal, consent, weakVendorEnforcement) + return args.Bool(0), args.Bool(1), args.Bool(2), args.Error(3) } -func (g *gdprPerms) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { - return g.allowHost, nil +// ErrReader returns an io.Reader that returns 0, err from all Read calls. This is added in +// Go 1.16. Copied here for now until we switch over. +func ErrReader(err error) io.Reader { + return &errReader{err: err} } -func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { - _, ok := g.allowedBidders[bidder] - return ok, nil +type errReader struct { + err 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 +func (r *errReader) Read(p []byte) (int, error) { + return 0, r.err } diff --git a/endpoints/events/event.go b/endpoints/events/event.go index fe178d8f271..5974ba9f7d8 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -4,16 +4,18 @@ import ( "context" "errors" "fmt" + "net/http" + "net/url" + "strconv" + "time" + "github.com/julienschmidt/httprouter" accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/stored_requests" - "net/http" - "net/url" - "strconv" - "time" + "github.com/prebid/prebid-server/util/httputil" ) const ( @@ -30,26 +32,11 @@ const ( AnalyticsParameter = "x" ) -var trackingPixelPng = &trackingPixel{ - Content: []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, - 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, - 0x89, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64, 0x88, - 0x00, 0x00, 0x00, 0x0D, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, - 0x00, 0x05, 0x00, 0x01, 0x87, 0xA1, 0x4E, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, - 0x42, 0x60, 0x82}, - ContentType: "image/png", -} - -type trackingPixel struct { - Content []byte `json:"content,omitempty"` - ContentType string `json:"content_type,omitempty"` -} - type eventEndpoint struct { Accounts stored_requests.AccountFetcher Analytics analytics.PBSAnalyticsModule Cfg *config.Configuration - TrackingPixel *trackingPixel + TrackingPixel *httputil.Pixel } func NewEventEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, analytics analytics.PBSAnalyticsModule) httprouter.Handle { @@ -57,7 +44,7 @@ func NewEventEndpoint(cfg *config.Configuration, accounts stored_requests.Accoun Accounts: accounts, Analytics: analytics, Cfg: cfg, - TrackingPixel: trackingPixelPng, + TrackingPixel: &httputil.Pixel1x1PNG, } return ee.Handle diff --git a/endpoints/getuids.go b/endpoints/getuids.go index 859c0e7288c..ad984a8df00 100644 --- a/endpoints/getuids.go +++ b/endpoints/getuids.go @@ -18,7 +18,7 @@ type userSyncs struct { // returns all the existing syncs for the user func NewGetUIDsEndpoint(cfg config.HostCookie) httprouter.Handle { return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - pc := usersync.ParsePBSCookieFromRequest(r, &cfg) + pc := usersync.ParseCookieFromRequest(r, &cfg) userSyncs := new(userSyncs) userSyncs.BuyerUIDs = pc.GetUIDs() json.NewEncoder(w).Encode(userSyncs) diff --git a/endpoints/httprouterhandler.go b/endpoints/httprouterhandler.go new file mode 100644 index 00000000000..118b1c31b06 --- /dev/null +++ b/endpoints/httprouterhandler.go @@ -0,0 +1,11 @@ +package endpoints + +import ( + "net/http" + + "github.com/julienschmidt/httprouter" +) + +type HTTPRouterHandler interface { + Handle(http.ResponseWriter, *http.Request, httprouter.Params) +} diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 6b4fa6890b8..7a0afb0d0d6 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -11,10 +11,6 @@ import ( "strings" "time" - "github.com/buger/jsonparser" - "github.com/golang/glog" - "github.com/julienschmidt/httprouter" - "github.com/mxmCherry/openrtb/v15/openrtb2" accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/amp" "github.com/prebid/prebid-server/analytics" @@ -30,6 +26,11 @@ import ( "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/iputil" + + "github.com/buger/jsonparser" + "github.com/golang/glog" + "github.com/julienschmidt/httprouter" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const defaultAmpRequestTimeoutMillis = 900 @@ -152,11 +153,11 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h } defer cancel() - usersyncs := usersync.ParsePBSCookieFromRequest(r, &(deps.cfg.HostCookie)) - if usersyncs.LiveSyncCount() == 0 { - labels.CookieFlag = metrics.CookieFlagNo - } else { + usersyncs := usersync.ParseCookieFromRequest(r, &(deps.cfg.HostCookie)) + if usersyncs.HasAnyLiveSyncs() { labels.CookieFlag = metrics.CookieFlagYes + } else { + labels.CookieFlag = metrics.CookieFlagNo } labels.PubID = getAccountID(req.Site.Publisher) // Look up account now that we have resolved the pubID value diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 2ce1180d50b..667c4aefd39 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -11,16 +11,15 @@ import ( "strconv" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/metrics" + metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" - gometrics "github.com/rcrowley/go-metrics" + "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" + + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -38,15 +37,13 @@ func TestGoodAmpRequests(t *testing.T) { "9": json.RawMessage(validRequest(t, "user.json")), } - // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. - // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. endpoint, _ := NewAmpEndpoint( &mockAmpExchange{}, newParamsValidator(t), &mockAmpStoredReqFetcher{goodRequests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -99,7 +96,7 @@ func TestAMPPageInfo(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -196,7 +193,7 @@ func TestGDPRConsent(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -348,7 +345,7 @@ func TestCCPAConsent(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -458,7 +455,7 @@ func TestConsentWarnings(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -550,7 +547,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -601,7 +598,7 @@ func TestAMPSiteExt(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), nil, nil, @@ -637,7 +634,7 @@ func TestAmpBadRequests(t *testing.T) { &mockAmpStoredReqFetcher{badRequests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -667,7 +664,7 @@ func TestAmpDebug(t *testing.T) { &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -739,7 +736,7 @@ func TestQueryParamOverrides(t *testing.T) { &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -891,7 +888,7 @@ func (s formatOverrideSpec) execute(t *testing.T) { &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -1270,7 +1267,7 @@ func TestBuildAmpObject(t *testing.T) { mockAmpFetcher, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, logger, map[string]string{}, []byte{}, @@ -1289,7 +1286,3 @@ func TestBuildAmpObject(t *testing.T) { assert.Equalf(t, test.expectedAmpObject.Origin, actualAmpObject.Origin, "Amp Object Origin field doesn't match expected: %s\n", test.description) } } - -func newTestMetrics() *metrics.Metrics { - return metrics.NewMetrics(gometrics.NewRegistry(), openrtb_ext.CoreBidderNames(), config.DisabledMetrics{}) -} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index bcdf133cd2a..fea391918df 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -151,17 +151,17 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http defer cancel() } - usersyncs := usersync.ParsePBSCookieFromRequest(r, &(deps.cfg.HostCookie)) + usersyncs := usersync.ParseCookieFromRequest(r, &(deps.cfg.HostCookie)) if req.App != nil { labels.Source = metrics.DemandApp labels.RType = metrics.ReqTypeORTB2App labels.PubID = getAccountID(req.App.Publisher) } else { //req.Site != nil labels.Source = metrics.DemandWeb - if usersyncs.LiveSyncCount() == 0 { - labels.CookieFlag = metrics.CookieFlagNo - } else { + if usersyncs.HasAnyLiveSyncs() { labels.CookieFlag = metrics.CookieFlagYes + } else { + labels.CookieFlag = metrics.CookieFlagNo } labels.PubID = getAccountID(req.Site.Publisher) } diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index e55ffd11093..ecc0554f45a 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -7,14 +7,15 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/currency" - 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/exchange" "github.com/prebid/prebid-server/gdpr" + metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/usersync" ) // dummyServer returns the header bidding test ad. This response was scraped from a real appnexus server response. @@ -66,7 +67,9 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { return } - adapters, adaptersErr := exchange.BuildAdapters(server.Client(), &config.Configuration{}, infos, newTestMetrics()) + nilMetrics := &metricsConfig.DummyMetricsEngine{} + + adapters, adaptersErr := exchange.BuildAdapters(server.Client(), &config.Configuration{}, infos, nilMetrics) if adaptersErr != nil { b.Fatal("unable to build adapters") } @@ -75,7 +78,8 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { adapters, nil, &config.Configuration{}, - newTestMetrics(), + map[string]usersync.Syncer{}, + nilMetrics, infos, gdpr.AlwaysAllow{}, currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), @@ -88,7 +92,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + nilMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 91910560476..748107c8d44 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -17,20 +17,22 @@ import ( "testing" "time" - "github.com/buger/jsonparser" - "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" + metricsConfig "github.com/prebid/prebid-server/metrics/config" "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/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/native1" + nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -426,15 +428,14 @@ func TestExplicitUserId(t *testing.T) { Name: cookieName, Value: mockId, }) - // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. - // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. + endpoint, _ := NewEndpoint( ex, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -475,7 +476,7 @@ func doRequest(t *testing.T, test testCase) (int, string) { BlacklistedAcctMap: test.Config.getBlackListedAccountMap(), AccountRequired: test.Config.AccountRequired, }, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, []byte(test.Config.AliasJSON), @@ -538,7 +539,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, @@ -588,7 +589,7 @@ func TestNilExchange(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap()) @@ -608,7 +609,7 @@ func TestNilValidator(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -629,7 +630,7 @@ func TestExchangeError(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -751,7 +752,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -945,7 +946,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -1008,7 +1009,7 @@ func TestStoredRequests(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1054,7 +1055,7 @@ func TestStoredRequestsVideoErrors(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1090,7 +1091,7 @@ func TestOversizedRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody) - 1)}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1125,7 +1126,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1158,7 +1159,7 @@ func TestNoEncoding(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -1233,7 +1234,7 @@ func TestContentType(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -1553,7 +1554,7 @@ func TestValidateImpExt(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(8096)}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."}, false, @@ -1599,7 +1600,7 @@ func TestCurrencyTrunc(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1643,7 +1644,7 @@ func TestCCPAInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1691,7 +1692,7 @@ func TestNoSaleInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1742,7 +1743,7 @@ func TestValidateSourceTID(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1783,7 +1784,7 @@ func TestSChainInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -2002,7 +2003,7 @@ func TestEidPermissionsInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -2252,7 +2253,7 @@ func TestIOS14EndToEnd(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -2280,7 +2281,7 @@ func TestAuctionWarnings(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 227f6c4a943..c7e3dfbde3e 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -255,16 +255,16 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re defer cancel() } - usersyncs := usersync.ParsePBSCookieFromRequest(r, &(deps.cfg.HostCookie)) + usersyncs := usersync.ParseCookieFromRequest(r, &(deps.cfg.HostCookie)) if bidReq.App != nil { labels.Source = metrics.DemandApp labels.PubID = getAccountID(bidReq.App.Publisher) } else { // both bidReq.App == nil and bidReq.Site != nil are true labels.Source = metrics.DemandWeb - if usersyncs.LiveSyncCount() == 0 { - labels.CookieFlag = metrics.CookieFlagNo - } else { + if usersyncs.HasAnyLiveSyncs() { labels.CookieFlag = metrics.CookieFlagYes + } else { + labels.CookieFlag = metrics.CookieFlagNo } labels.PubID = getAccountID(bidReq.Site.Publisher) } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 5452d6c2c39..b00467d95c3 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -12,15 +12,18 @@ import ( "strings" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/metrics" + metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + gometrics "github.com/rcrowley/go-metrics" "github.com/stretchr/testify/assert" ) @@ -1203,7 +1206,9 @@ func TestFormatTargetingKeyLongKey(t *testing.T) { func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *metrics.Metrics, *mockAnalyticsModule) { mockModule := &mockAnalyticsModule{} - metrics := newTestMetrics() + + metrics := metrics.NewMetrics(gometrics.NewRegistry(), openrtb_ext.CoreBidderNames(), config.DisabledMetrics{}, nil) + deps := &endpointDeps{ ex, newParamsValidator(t), @@ -1221,7 +1226,6 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m nil, hardcodedResponseIPValidator{response: true}, } - return deps, metrics, mockModule } @@ -1254,7 +1258,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1276,7 +1280,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1298,7 +1302,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), + &metricsConfig.DummyMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, diff --git a/endpoints/setuid.go b/endpoints/setuid.go index 4bff02acf37..014ff3122c1 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -14,8 +14,8 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/httputil" ) const ( @@ -26,12 +26,14 @@ const ( chromeiOSStrLen = len(chromeiOSStr) ) -func NewSetUIDEndpoint(cfg config.HostCookie, syncers map[openrtb_ext.BidderName]usersync.Usersyncer, perms gdpr.Permissions, pbsanalytics analytics.PBSAnalyticsModule, metricsEngine metrics.MetricsEngine) httprouter.Handle { +func NewSetUIDEndpoint(cfg config.HostCookie, syncersByBidder map[string]usersync.Syncer, perms gdpr.Permissions, pbsanalytics analytics.PBSAnalyticsModule, metricsEngine metrics.MetricsEngine) httprouter.Handle { cookieTTL := time.Duration(cfg.TTL) * 24 * time.Hour - validFamilyNameMap := make(map[string]struct{}) - for _, s := range syncers { - validFamilyNameMap[s.FamilyName()] = struct{}{} + // convert map of syncers by bidder to map of syncers by key + // - its safe to assume that if multiple bidders map to the same key, the syncers are interchangeable. + syncersByKey := make(map[string]usersync.Syncer, len(syncersByBidder)) + for _, v := range syncersByBidder { + syncersByKey[v.Key()] = v } return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -42,37 +44,44 @@ func NewSetUIDEndpoint(cfg config.HostCookie, syncers map[openrtb_ext.BidderName defer pbsanalytics.LogSetUIDObject(&so) - pc := usersync.ParsePBSCookieFromRequest(r, &cfg) + pc := usersync.ParseCookieFromRequest(r, &cfg) if !pc.AllowSyncs() { w.WriteHeader(http.StatusUnauthorized) - metricsEngine.RecordUserIDSet(metrics.UserLabels{ - Action: metrics.RequestActionOptOut, - }) + metricsEngine.RecordSetUid(metrics.SetUidOptOut) so.Status = http.StatusUnauthorized return } query := r.URL.Query() - familyName, err := getFamilyName(query, validFamilyNameMap) + syncer, err := getSyncer(query, syncersByKey) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) - metricsEngine.RecordUserIDSet(metrics.UserLabels{ - Action: metrics.RequestActionErr, - }) + metricsEngine.RecordSetUid(metrics.SetUidSyncerUnknown) + so.Status = http.StatusBadRequest + return + } + so.Bidder = syncer.Key() + + responseFormat, err := getResponseFormat(query, syncer) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + metricsEngine.RecordSetUid(metrics.SetUidBadRequest) so.Status = http.StatusBadRequest return } - so.Bidder = familyName if shouldReturn, status, body := preventSyncsGDPR(query.Get("gdpr"), query.Get("gdpr_consent"), perms); shouldReturn { w.WriteHeader(status) w.Write([]byte(body)) - metricsEngine.RecordUserIDSet(metrics.UserLabels{ - Action: metrics.RequestActionGDPR, - Bidder: openrtb_ext.BidderName(familyName), - }) + switch status { + case http.StatusBadRequest: + metricsEngine.RecordSetUid(metrics.SetUidBadRequest) + case http.StatusUnavailableForLegalReasons: + metricsEngine.RecordSetUid(metrics.SetUidGDPRHostCookieBlocked) + } so.Status = status return } @@ -81,38 +90,70 @@ func NewSetUIDEndpoint(cfg config.HostCookie, syncers map[openrtb_ext.BidderName so.UID = uid if uid == "" { - pc.Unsync(familyName) - } else { - err = pc.TrySync(familyName, uid) - } - - if err == nil { - labels := metrics.UserLabels{ - Action: metrics.RequestActionSet, - Bidder: openrtb_ext.BidderName(familyName), - } - metricsEngine.RecordUserIDSet(labels) + pc.Unsync(syncer.Key()) + metricsEngine.RecordSetUid(metrics.SetUidOK) + metricsEngine.RecordSyncerSet(syncer.Key(), metrics.SyncerSetUidCleared) + so.Success = true + } else if err = pc.TrySync(syncer.Key(), uid); err == nil { + metricsEngine.RecordSetUid(metrics.SetUidOK) + metricsEngine.RecordSyncerSet(syncer.Key(), metrics.SyncerSetUidOK) so.Success = true } setSiteCookie := siteCookieCheck(r.UserAgent()) pc.SetCookieOnResponse(w, setSiteCookie, &cfg, cookieTTL) + + switch responseFormat { + case "i": + w.Header().Add("Content-Type", httputil.Pixel1x1PNG.ContentType) + w.Header().Add("Content-Length", strconv.Itoa(len(httputil.Pixel1x1PNG.Content))) + w.WriteHeader(http.StatusOK) + w.Write(httputil.Pixel1x1PNG.Content) + case "b": + w.Header().Add("Content-Type", "text/html") + w.Header().Add("Content-Length", "0") + w.WriteHeader(http.StatusOK) + } }) } -func getFamilyName(query url.Values, validFamilyNameMap map[string]struct{}) (string, error) { - // The family name is bound to the 'bidder' query param. In most cases, these values are the same. - familyName := query.Get("bidder") +func getSyncer(query url.Values, syncersByKey map[string]usersync.Syncer) (usersync.Syncer, error) { + key := query.Get("bidder") - if familyName == "" { - return "", errors.New(`"bidder" query param is required`) + if key == "" { + return nil, errors.New(`"bidder" query param is required`) } - if _, ok := validFamilyNameMap[familyName]; !ok { - return "", errors.New("The bidder name provided is not supported by Prebid Server") + syncer, syncerExists := syncersByKey[key] + if !syncerExists { + return nil, errors.New("The bidder name provided is not supported by Prebid Server") } - return familyName, nil + return syncer, nil +} + +// getResponseFormat reads the format query parameter or falls back to the syncer's default. +// Returns either "b" (iframe), "i" (redirect), or an empty string "" (legacy behavior of an +// empty response body with no content type). +func getResponseFormat(query url.Values, syncer usersync.Syncer) (string, error) { + format, formatProvided := query["f"] + formatEmpty := len(format) == 0 || format[0] == "" + + if !formatProvided || formatEmpty { + switch syncer.DefaultSyncType() { + case usersync.SyncTypeIFrame: + return "b", nil + case usersync.SyncTypeRedirect: + return "i", nil + default: + return "", nil + } + } + + if !strings.EqualFold(format[0], "b") && !strings.EqualFold(format[0], "i") { + return "", errors.New(`"f" query param is invalid. must be "b" or "i"`) + } + return strings.ToLower(format[0]), nil } // siteCookieCheck scans the input User Agent string to check if browser is Chrome and browser version is greater than the minimum version for adding the SameSite cookie attribute @@ -126,6 +167,7 @@ func siteCookieCheck(ua string) bool { } else if criOSIndex != -1 { result = checkChromeBrowserVersion(ua, criOSIndex, chromeiOSStrLen) } + return result } @@ -144,7 +186,6 @@ func checkChromeBrowserVersion(ua string, index int, chromeStrLength int) bool { } func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permissions) (shouldReturn bool, status int, body string) { - if gdprEnabled != "" && gdprEnabled != "0" && gdprEnabled != "1" { return true, http.StatusBadRequest, "the gdpr query param must be either 0 or 1. You gave " + gdprEnabled } @@ -165,10 +206,10 @@ func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permiss return true, http.StatusBadRequest, "gdpr_consent was invalid. " + err.Error() } - // We can't really distinguish between requests that are for a new version of the global vendor list, and - // ones which are simply malformed (version number is much too large). - // Since we try to fetch new versions as requests come in for them, PBS *should* self-correct - // rather quickly, meaning that most of these will be malformed strings. + // We can't distinguish between requests for a new version of the global vendor list, and requests + // which are malformed (version number is much too large). Since we try to fetch new versions as we + // receive requests, PBS *should* self-correct quickly, allowing us to assume most of the errors + // caught here will be malformed strings. return true, http.StatusBadRequest, "No global vendor list was available to interpret this consent string. If this is a new, valid version, it should become available soon." } @@ -176,5 +217,5 @@ func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permiss return false, 0, "" } - return true, http.StatusOK, "The gdpr_consent string prevents cookies from being saved" + return true, http.StatusUnavailableForLegalReasons, "The gdpr_consent string prevents cookies from being saved" } diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index caeb09a858c..976946078c7 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -13,180 +13,232 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/usersync" "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/openrtb_ext" - analyticsConf "github.com/prebid/prebid-server/analytics/config" metricsConf "github.com/prebid/prebid-server/metrics/config" ) func TestSetUIDEndpoint(t *testing.T) { testCases := []struct { - uri string - validFamilyNames []string - existingSyncs map[string]string - gdprAllowsHostCookies bool - gdprReturnsError bool - expectedSyncs map[string]string - expectedRespMessage string - expectedResponseCode int - description string + uri string + syncersBidderNameToKey map[string]string + existingSyncs map[string]string + gdprAllowsHostCookies bool + gdprReturnsError bool + gdprMalformed bool + expectedSyncs map[string]string + expectedBody string + expectedStatusCode int + expectedHeaders map[string]string + description string }{ { - uri: "/setuid?bidder=pubmatic&uid=123", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: nil, - gdprAllowsHostCookies: true, - expectedSyncs: map[string]string{"pubmatic": "123"}, - expectedResponseCode: http.StatusOK, - description: "Set uid for valid bidder", - }, - { - uri: "/setuid?bidder=unsupported-bidder&uid=123", - validFamilyNames: []string{}, - existingSyncs: nil, - gdprAllowsHostCookies: true, - expectedSyncs: nil, - expectedResponseCode: http.StatusBadRequest, - description: "Don't set uid for an unsupported bidder", - }, - { - uri: "/setuid?bidder=&uid=123", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: nil, - gdprAllowsHostCookies: true, - expectedSyncs: nil, - expectedResponseCode: http.StatusBadRequest, - description: "Don't set uid for an empty bidder", - }, - { - uri: "/setuid?bidder=unsupported-bidder&uid=123", - validFamilyNames: []string{}, - existingSyncs: map[string]string{"pubmatic": "1234"}, - gdprAllowsHostCookies: true, - expectedSyncs: nil, - expectedResponseCode: http.StatusBadRequest, + uri: "/setuid?bidder=pubmatic&uid=123", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Set uid for valid bidder", + }, + { + uri: "/setuid?bidder=adnxs&uid=123", + syncersBidderNameToKey: map[string]string{"appnexus": "adnxs"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"adnxs": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Set uid for valid bidder with different key", + }, + { + uri: "/setuid?bidder=unsupported-bidder&uid=123", + syncersBidderNameToKey: map[string]string{}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: nil, + expectedStatusCode: http.StatusBadRequest, + expectedBody: "The bidder name provided is not supported by Prebid Server", + description: "Don't set uid for an unsupported bidder", + }, + { + uri: "/setuid?bidder=&uid=123", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: nil, + expectedStatusCode: http.StatusBadRequest, + expectedBody: `"bidder" query param is required`, + description: "Don't set uid for an empty bidder", + }, + { + uri: "/setuid?bidder=unsupported-bidder&uid=123", + syncersBidderNameToKey: map[string]string{}, + existingSyncs: map[string]string{"pubmatic": "1234"}, + gdprAllowsHostCookies: true, + expectedSyncs: nil, + expectedStatusCode: http.StatusBadRequest, + expectedBody: "The bidder name provided is not supported by Prebid Server", description: "No need to set existing syncs back in response for a request " + "to set uid for an unsupported bidder", }, { - uri: "/setuid?bidder=&uid=123", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: map[string]string{"pubmatic": "1234"}, - gdprAllowsHostCookies: true, - expectedSyncs: nil, - expectedResponseCode: http.StatusBadRequest, + uri: "/setuid?bidder=&uid=123", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: map[string]string{"pubmatic": "1234"}, + gdprAllowsHostCookies: true, + expectedSyncs: nil, + expectedStatusCode: http.StatusBadRequest, + expectedBody: `"bidder" query param is required`, description: "No need to set existing syncs back in response for a request " + "to set uid for an empty bidder", }, { - uri: "/setuid?bidder=pubmatic", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: map[string]string{"pubmatic": "1234"}, - gdprAllowsHostCookies: true, - expectedSyncs: map[string]string{}, - expectedResponseCode: http.StatusOK, - description: "Unset uid for a bidder if the request contains an empty uid for that bidder", - }, - { - uri: "/setuid?bidder=pubmatic&uid=123", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: map[string]string{"rubicon": "def"}, - gdprAllowsHostCookies: true, - expectedSyncs: map[string]string{"pubmatic": "123", "rubicon": "def"}, - expectedResponseCode: http.StatusOK, - description: "Add the uid for the requested bidder to the list of existing syncs", - }, - { - uri: "/setuid?bidder=pubmatic&uid=123&gdpr=0", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: nil, - gdprAllowsHostCookies: true, - expectedSyncs: map[string]string{"pubmatic": "123"}, - expectedResponseCode: http.StatusOK, - description: "Don't care about GDPR consent if GDPR is set to 0", - }, - { - uri: "/setuid?bidder=pubmatic&uid=123", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: nil, - expectedSyncs: nil, - expectedResponseCode: http.StatusOK, - expectedRespMessage: "The gdpr_consent string prevents cookies from being saved", - description: "Return err message if the GDPR consent doesn't allow syncs for the given bidder", - }, - { - uri: "/setuid?uid=123", - validFamilyNames: []string{"appnexus"}, - existingSyncs: nil, - expectedSyncs: nil, - gdprAllowsHostCookies: true, - expectedResponseCode: http.StatusBadRequest, - expectedRespMessage: `"bidder" query param is required`, - description: "Return an error if the bidder param is missing from the request", - }, - { - uri: "/setuid?bidder=appnexus&uid=123&gdpr=2", - validFamilyNames: []string{"appnexus"}, - existingSyncs: nil, - expectedSyncs: nil, - gdprAllowsHostCookies: true, - expectedResponseCode: http.StatusBadRequest, - expectedRespMessage: "the gdpr query param must be either 0 or 1. You gave 2", - description: "Return an error if GDPR is set to anything else other that 0 or 1", - }, - { - uri: "/setuid?bidder=appnexus&uid=123&gdpr=1", - validFamilyNames: []string{"appnexus"}, - existingSyncs: nil, - expectedSyncs: nil, - gdprAllowsHostCookies: true, - expectedResponseCode: http.StatusBadRequest, - expectedRespMessage: "gdpr_consent is required when gdpr=1", - description: "Return an error if GDPR is set to 1 but GDPR consent string is missing", + uri: "/setuid?bidder=pubmatic", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: map[string]string{"pubmatic": "1234"}, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Unset uid for a bidder if the request contains an empty uid for that bidder", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: map[string]string{"rubicon": "def"}, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123", "rubicon": "def"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Add the uid for the requested bidder to the list of existing syncs", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&gdpr=0", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Don't care about GDPR consent if GDPR is set to 0", + }, + { + uri: "/setuid?uid=123", + syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, + existingSyncs: nil, + expectedSyncs: nil, + gdprAllowsHostCookies: true, + expectedStatusCode: http.StatusBadRequest, + expectedBody: `"bidder" query param is required`, + description: "Return an error if the bidder param is missing from the request", + }, + { + uri: "/setuid?bidder=appnexus&uid=123&gdpr=2", + syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, + existingSyncs: nil, + expectedSyncs: nil, + gdprAllowsHostCookies: true, + expectedStatusCode: http.StatusBadRequest, + expectedBody: "the gdpr query param must be either 0 or 1. You gave 2", + description: "Return an error if GDPR is set to anything else other that 0 or 1", + }, + { + uri: "/setuid?bidder=appnexus&uid=123&gdpr=1", + syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, + existingSyncs: nil, + expectedSyncs: nil, + gdprAllowsHostCookies: true, + expectedStatusCode: http.StatusBadRequest, + expectedBody: "gdpr_consent is required when gdpr=1", + description: "Return an error if GDPR is set to 1 but GDPR consent string is missing", }, { uri: "/setuid?bidder=pubmatic&uid=123&gdpr_consent=" + "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: nil, - expectedSyncs: nil, - gdprReturnsError: true, - expectedResponseCode: http.StatusBadRequest, - expectedRespMessage: "No global vendor list was available to interpret this consent string. " + + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + expectedSyncs: nil, + gdprReturnsError: true, + expectedStatusCode: http.StatusBadRequest, + expectedBody: "No global vendor list was available to interpret this consent string. " + "If this is a new, valid version, it should become available soon.", description: "Return an error if the GDPR string is either malformed or using a newer version that isn't yet supported", }, { uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=" + "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - validFamilyNames: []string{"pubmatic"}, - existingSyncs: nil, - expectedSyncs: nil, - expectedResponseCode: http.StatusOK, - expectedRespMessage: "The gdpr_consent string prevents cookies from being saved", - description: "Shouldn't set uid for a bidder if it is not allowed by the GDPR consent string", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + expectedSyncs: nil, + expectedStatusCode: http.StatusUnavailableForLegalReasons, + expectedBody: "The gdpr_consent string prevents cookies from being saved", + description: "Shouldn't set uid for a bidder if it is not allowed by the GDPR consent string", }, { uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=" + "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", - validFamilyNames: []string{"pubmatic"}, - gdprAllowsHostCookies: true, - existingSyncs: nil, - expectedSyncs: map[string]string{"pubmatic": "123"}, - expectedResponseCode: http.StatusOK, - description: "Should set uid for a bidder that is allowed by the GDPR consent string", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + existingSyncs: nil, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Should set uid for a bidder that is allowed by the GDPR consent string", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=" + + "malformed", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + gdprMalformed: true, + existingSyncs: nil, + expectedStatusCode: http.StatusBadRequest, + expectedBody: "gdpr_consent was invalid. malformed consent string malformed: some error", + description: "Should return an error if GDPR consent string is malformed", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&f=b", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, + description: "Set uid for valid bidder with iframe format", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&f=i", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: map[string]string{"pubmatic": "123"}, + expectedStatusCode: http.StatusOK, + expectedHeaders: map[string]string{"Content-Type": "image/png", "Content-Length": "86"}, + description: "Set uid for valid bidder with redirect format", + }, + { + uri: "/setuid?bidder=pubmatic&uid=123&f=x", + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + existingSyncs: nil, + gdprAllowsHostCookies: true, + expectedSyncs: nil, + expectedStatusCode: http.StatusBadRequest, + expectedBody: `"f" query param is invalid. must be "b" or "i"`, + description: "Set uid for valid bidder with invalid format", }, } metrics := &metricsConf.DummyMetricsEngine{} for _, test := range testCases { response := doRequest(makeRequest(test.uri, test.existingSyncs), metrics, - test.validFamilyNames, test.gdprAllowsHostCookies, test.gdprReturnsError) - assert.Equal(t, test.expectedResponseCode, response.Code, "Test Case: %s. /setuid returned unexpected error code", test.description) + test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed) + assert.Equal(t, test.expectedStatusCode, response.Code, "Test Case: %s. /setuid returned unexpected error code", test.description) if test.expectedSyncs != nil { assertHasSyncs(t, test.description, response, test.expectedSyncs) @@ -194,86 +246,127 @@ func TestSetUIDEndpoint(t *testing.T) { assert.Equal(t, "", response.Header().Get("Set-Cookie"), "Test Case: %s. /setuid returned unexpected cookie", test.description) } - if test.expectedRespMessage != "" { - assert.Equal(t, test.expectedRespMessage, response.Body.String(), "Test Case: %s. /setuid returned unexpected message") + if test.expectedBody != "" { + assert.Equal(t, test.expectedBody, response.Body.String(), "Test Case: %s. /setuid returned unexpected message", test.description) } + + // compare header values, except for the cookies + responseHeaders := map[string]string{} + for k, v := range response.Result().Header { + if k != "Set-Cookie" { + responseHeaders[k] = v[0] + } + } + if test.expectedHeaders == nil { + test.expectedHeaders = map[string]string{} + } + assert.Equal(t, test.expectedHeaders, responseHeaders, test.description+":headers") } } func TestSetUIDEndpointMetrics(t *testing.T) { + cookieWithOptOut := usersync.NewCookie() + cookieWithOptOut.SetOptOut(true) + testCases := []struct { - uri string - cookies []*usersync.PBSCookie - validFamilyNames []string - gdprAllowsHostCookies bool - expectedMetricAction metrics.RequestAction - expectedMetricBidder openrtb_ext.BidderName - expectedResponseCode int - description string + description string + uri string + cookies []*usersync.Cookie + syncersBidderNameToKey map[string]string + gdprAllowsHostCookies bool + expectedResponseCode int + expectedMetrics func(*metrics.MetricsEngineMock) }{ { - uri: "/setuid?bidder=pubmatic&uid=123", - cookies: []*usersync.PBSCookie{}, - validFamilyNames: []string{"pubmatic"}, - gdprAllowsHostCookies: true, - expectedMetricAction: metrics.RequestActionSet, - expectedMetricBidder: openrtb_ext.BidderName("pubmatic"), - expectedResponseCode: 200, - description: "Success - Sync", - }, - { - uri: "/setuid?bidder=pubmatic&uid=", - cookies: []*usersync.PBSCookie{}, - validFamilyNames: []string{"pubmatic"}, - gdprAllowsHostCookies: true, - expectedMetricAction: metrics.RequestActionSet, - expectedMetricBidder: openrtb_ext.BidderName("pubmatic"), - expectedResponseCode: 200, - description: "Success - Unsync", - }, - { - uri: "/setuid?bidder=pubmatic&uid=123", - cookies: []*usersync.PBSCookie{usersync.NewPBSCookieWithOptOut()}, - validFamilyNames: []string{"pubmatic"}, - gdprAllowsHostCookies: true, - expectedMetricAction: metrics.RequestActionOptOut, - expectedResponseCode: 401, - description: "Cookie Opted Out", - }, - { - uri: "/setuid?bidder=pubmatic&uid=123", - cookies: []*usersync.PBSCookie{}, - validFamilyNames: []string{}, - gdprAllowsHostCookies: true, - expectedMetricAction: metrics.RequestActionErr, - expectedResponseCode: 400, - description: "Unsupported Cookie Name", - }, - { - uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1", - cookies: []*usersync.PBSCookie{}, - validFamilyNames: []string{"pubmatic"}, - gdprAllowsHostCookies: false, - expectedMetricAction: metrics.RequestActionGDPR, - expectedMetricBidder: openrtb_ext.BidderName("pubmatic"), - expectedResponseCode: 400, - description: "Prevented By GDPR", + description: "Success - Sync", + uri: "/setuid?bidder=pubmatic&uid=123", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + expectedResponseCode: 200, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidOK).Once() + m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidOK).Once() + }, + }, + { + description: "Success - Unsync", + uri: "/setuid?bidder=pubmatic&uid=", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + expectedResponseCode: 200, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidOK).Once() + m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidCleared).Once() + }, + }, + { + description: "Cookie Opted Out", + uri: "/setuid?bidder=pubmatic&uid=123", + cookies: []*usersync.Cookie{cookieWithOptOut}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + expectedResponseCode: 401, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidOptOut).Once() + }, + }, + { + description: "Unknown Syncer Key", + uri: "/setuid?bidder=pubmatic&uid=123", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{}, + gdprAllowsHostCookies: true, + expectedResponseCode: 400, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidSyncerUnknown).Once() + }, + }, + { + description: "Unknown Format", + uri: "/setuid?bidder=pubmatic&uid=123&f=z", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + expectedResponseCode: 400, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidBadRequest).Once() + }, + }, + { + description: "Prevented By GDPR - Invalid Consent String", + uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: true, + expectedResponseCode: 400, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidBadRequest).Once() + }, + }, + { + description: "Prevented By GDPR - Permission Denied By Consent String", + uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=any", + cookies: []*usersync.Cookie{}, + syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, + gdprAllowsHostCookies: false, + expectedResponseCode: 451, + expectedMetrics: func(m *metrics.MetricsEngineMock) { + m.On("RecordSetUid", metrics.SetUidGDPRHostCookieBlocked).Once() + }, }, } for _, test := range testCases { metricsEngine := &metrics.MetricsEngineMock{} - expectedLabels := metrics.UserLabels{ - Action: test.expectedMetricAction, - Bidder: test.expectedMetricBidder, - } - metricsEngine.On("RecordUserIDSet", expectedLabels).Once() + test.expectedMetrics(metricsEngine) req := httptest.NewRequest("GET", test.uri, nil) for _, v := range test.cookies { addCookie(req, v) } - response := doRequest(req, metricsEngine, test.validFamilyNames, test.gdprAllowsHostCookies, false) + response := doRequest(req, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false) assert.Equal(t, test.expectedResponseCode, response.Code, test.description) metricsEngine.AssertExpectations(t) @@ -282,12 +375,12 @@ func TestSetUIDEndpointMetrics(t *testing.T) { func TestOptedOut(t *testing.T) { request := httptest.NewRequest("GET", "/setuid?bidder=pubmatic&uid=123", nil) - cookie := usersync.NewPBSCookie() - cookie.SetPreference(false) + cookie := usersync.NewCookie() + cookie.SetOptOut(true) addCookie(request, cookie) - validFamilyNames := []string{"pubmatic"} + syncersBidderNameToKey := map[string]string{"pubmatic": "pubmatic"} metrics := &metricsConf.DummyMetricsEngine{} - response := doRequest(request, metrics, validFamilyNames, true, false) + response := doRequest(request, metrics, syncersBidderNameToKey, true, false, false) assert.Equal(t, http.StatusUnauthorized, response.Code) } @@ -315,50 +408,85 @@ func TestSiteCookieCheck(t *testing.T) { } } -func TestGetFamilyName(t *testing.T) { +func TestGetResponseFormat(t *testing.T) { testCases := []struct { - urlValues url.Values - expectedName string - expectedError string - description string + urlValues url.Values + syncer usersync.Syncer + expectedFormat string + expectedError string + description string }{ { - urlValues: url.Values{"bidder": []string{"valid"}}, - expectedName: "valid", - description: "Should return no error for valid family name", + urlValues: url.Values{}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame}, + expectedFormat: "b", + description: "parameter not provided, use default sync type iframe", + }, + { + urlValues: url.Values{}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, + expectedFormat: "i", + description: "parameter not provided, use default sync type redirect", + }, + { + urlValues: url.Values{}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncType("invalid")}, + expectedFormat: "", + description: "parameter not provided, default sync type is invalid", + }, + { + urlValues: url.Values{"f": []string{"b"}}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, + expectedFormat: "b", + description: "parameter given as `b`, default sync type is opposite", + }, + { + urlValues: url.Values{"f": []string{"B"}}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, + expectedFormat: "b", + description: "parameter given as `b`, default sync type is opposite - case insensitive", }, { - urlValues: url.Values{"bidder": []string{"VALID"}}, - expectedError: "The bidder name provided is not supported by Prebid Server", - description: "Should return error for different case", + urlValues: url.Values{"f": []string{"i"}}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame}, + expectedFormat: "i", + description: "parameter given as `b`, default sync type is opposite", }, { - urlValues: url.Values{"bidder": []string{"invalid"}}, - expectedError: "The bidder name provided is not supported by Prebid Server", - description: "Should return an error for unsupported bidder", + urlValues: url.Values{"f": []string{"I"}}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame}, + expectedFormat: "i", + description: "parameter given as `b`, default sync type is opposite - case insensitive", }, { - urlValues: url.Values{"bidder": []string{}}, - expectedError: `"bidder" query param is required`, - description: "Should return an error for empty bidder name", + urlValues: url.Values{"f": []string{"x"}}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame}, + expectedError: `"f" query param is invalid. must be "b" or "i"`, + description: "parameter given invalid", }, { - urlValues: url.Values{}, - expectedError: `"bidder" query param is required`, - description: "Should return an error for missing bidder name", + urlValues: url.Values{"f": []string{}}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, + expectedFormat: "i", + description: "parameter given is empty (by slice), use default sync type redirect", + }, + { + urlValues: url.Values{"f": []string{""}}, + syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, + expectedFormat: "i", + description: "parameter given is empty (by empty item), use default sync type redirect", }, } for _, test := range testCases { + result, err := getResponseFormat(test.urlValues, test.syncer) - name, err := getFamilyName(test.urlValues, map[string]struct{}{"valid": {}}) - - assert.Equal(t, test.expectedName, name, test.description) - - if test.expectedError != "" { - assert.EqualError(t, err, test.expectedError, test.description) + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + assert.Equal(t, test.expectedFormat, result, test.description+":result") } else { - assert.NoError(t, err, test.description) + assert.EqualError(t, err, test.expectedError, test.description+":err") + assert.Empty(t, result, test.description+":result") } } } @@ -366,7 +494,9 @@ func TestGetFamilyName(t *testing.T) { func assertHasSyncs(t *testing.T, testCase string, resp *httptest.ResponseRecorder, syncs map[string]string) { t.Helper() cookie := parseCookieString(t, resp) - assert.Equal(t, len(syncs), cookie.LiveSyncCount(), "Test Case: %s. /setuid response doesn't contain expected number of syncs", testCase) + + assert.Equal(t, len(syncs), len(cookie.GetUIDs()), "Test Case: %s. /setuid response doesn't contain expected number of syncs", testCase) + for bidder, uid := range syncs { assert.True(t, cookie.HasLiveSync(bidder), "Test Case: %s. /setuid response cookie doesn't contain uid for bidder: %s", testCase, bidder) actualUID, _, _ := cookie.GetUID(bidder) @@ -377,39 +507,40 @@ func assertHasSyncs(t *testing.T, testCase string, resp *httptest.ResponseRecord func makeRequest(uri string, existingSyncs map[string]string) *http.Request { request := httptest.NewRequest("GET", uri, nil) if len(existingSyncs) > 0 { - pbsCookie := usersync.NewPBSCookie() - for family, value := range existingSyncs { - pbsCookie.TrySync(family, value) + pbsCookie := usersync.NewCookie() + for key, value := range existingSyncs { + pbsCookie.TrySync(key, value) } addCookie(request, pbsCookie) } return request } -func doRequest(req *http.Request, metrics metrics.MetricsEngine, validFamilyNames []string, gdprAllowsHostCookies bool, gdprReturnsError bool) *httptest.ResponseRecorder { +func doRequest(req *http.Request, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError bool) *httptest.ResponseRecorder { cfg := config.Configuration{} perms := &mockPermsSetUID{ allowHost: gdprAllowsHostCookies, errorHost: gdprReturnsError, + errorMalformed: gdprReturnsMalformedError, personalInfoAllowed: true, } analytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics) - syncers := make(map[openrtb_ext.BidderName]usersync.Usersyncer) - for _, name := range validFamilyNames { - syncers[openrtb_ext.BidderName(name)] = newFakeSyncer(name) + syncersByBidder := make(map[string]usersync.Syncer) + for bidderName, syncerKey := range syncersBidderNameToKey { + syncersByBidder[bidderName] = fakeSyncer{key: syncerKey, defaultSyncType: usersync.SyncTypeIFrame} } - endpoint := NewSetUIDEndpoint(cfg.HostCookie, syncers, perms, analytics, metrics) + endpoint := NewSetUIDEndpoint(cfg.HostCookie, syncersByBidder, perms, analytics, metrics) response := httptest.NewRecorder() endpoint(response, req, nil) return response } -func addCookie(req *http.Request, cookie *usersync.PBSCookie) { +func addCookie(req *http.Request, cookie *usersync.Cookie) { req.AddCookie(cookie.ToHTTPCookie(time.Duration(1) * time.Hour)) } -func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *usersync.PBSCookie { +func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *usersync.Cookie { cookieString := response.Header().Get("Set-Cookie") parser := regexp.MustCompile("uids=(.*?);") res := parser.FindStringSubmatch(cookieString) @@ -418,21 +549,24 @@ func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *users Name: "uids", Value: res[1], } - return usersync.ParsePBSCookie(&httpCookie) + return usersync.ParseCookie(&httpCookie) } type mockPermsSetUID struct { allowHost bool errorHost bool + errorMalformed bool personalInfoAllowed bool } func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { - var err error + if g.errorMalformed { + return g.allowHost, &gdpr.ErrorMalformedConsent{Consent: consent, Cause: errors.New("some error")} + } if g.errorHost { - err = errors.New("something went wrong") + return g.allowHost, errors.New("something went wrong") } - return g.allowHost, err + return g.allowHost, nil } func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { @@ -443,22 +577,23 @@ func (g *mockPermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidder o return g.personalInfoAllowed, g.personalInfoAllowed, g.personalInfoAllowed, nil } -func newFakeSyncer(familyName string) usersync.Usersyncer { - return fakeSyncer{ - familyName: familyName, - } +type fakeSyncer struct { + key string + defaultSyncType usersync.SyncType } -type fakeSyncer struct { - familyName string +func (s fakeSyncer) Key() string { + return s.key +} + +func (s fakeSyncer) DefaultSyncType() usersync.SyncType { + return s.defaultSyncType } -// FamilyNames implements the Usersyncer interface. -func (s fakeSyncer) FamilyName() string { - return s.familyName +func (s fakeSyncer) SupportsType(syncTypes []usersync.SyncType) bool { + return true } -// GetUsersyncInfo implements the Usersyncer interface with a no-op. -func (s fakeSyncer) GetUsersyncInfo(privacyPolicies privacy.Policies) (*usersync.UsersyncInfo, error) { - return nil, nil +func (s fakeSyncer) GetSync(syncTypes []usersync.SyncType, privacyPolicies privacy.Policies) (usersync.Sync, error) { + return usersync.Sync{}, nil } diff --git a/exchange/exchange.go b/exchange/exchange.go index 87b28782e59..c612a35bd3c 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,10 +14,6 @@ import ( "strings" "time" - "github.com/buger/jsonparser" - "github.com/gofrs/uuid" - "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" @@ -27,6 +23,12 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/usersync" + + "github.com/buger/jsonparser" + "github.com/gofrs/uuid" + "github.com/golang/glog" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type ContextKey string @@ -45,14 +47,14 @@ type Exchange interface { // IdFetcher can find the user's ID for a specific Bidder. type IdFetcher interface { - // GetId returns the ID for the bidder. The boolean will be true if the ID exists, and false otherwise. - GetId(bidder openrtb_ext.BidderName) (string, bool) - LiveSyncCount() int + GetUID(key string) (uid string, exists bool, notExpired bool) + HasAnyLiveSyncs() bool } type exchange struct { adapterMap map[openrtb_ext.BidderName]adaptedBidder bidderInfo config.BidderInfos + bidderToSyncerKey map[string]string me metrics.MetricsEngine cache prebid_cache_client.Client cacheTime time.Duration @@ -109,7 +111,12 @@ 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 { +func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange { + bidderToSyncerKey := map[string]string{} + for bidder, syncer := range syncersByBidder { + bidderToSyncerKey[bidder] = syncer.Key() + } + gdprDefaultValue := gdpr.SignalYes if cfg.GDPR.DefaultValue == "0" { gdprDefaultValue = gdpr.SignalNo @@ -118,6 +125,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid return &exchange{ adapterMap: adapters, bidderInfo: infos, + bidderToSyncerKey: bidderToSyncerKey, cache: cache, cacheTime: time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond, categoriesFetcher: categoriesFetcher, @@ -200,7 +208,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * 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, e.me, gdprDefaultValue, e.privacyConfig, &r.Account) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.bidderToSyncerKey, e.gDPR, e.me, gdprDefaultValue, e.privacyConfig, &r.Account) e.me.RecordRequestPrivacy(privacyLabels) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index ea8a7b84c88..706d118de4f 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -28,6 +28,7 @@ import ( pbc "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" + "github.com/prebid/prebid-server/usersync" "github.com/buger/jsonparser" "github.com/stretchr/testify/assert" @@ -65,7 +66,7 @@ func TestNewExchange(t *testing.T) { } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - e := NewExchange(adapters, nil, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) for _, bidderName := range knownAdapters { if _, ok := e.adapterMap[bidderName]; !ok { t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName) @@ -113,7 +114,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) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, @@ -1186,8 +1187,9 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { } adapterList := make([]openrtb_ext.BidderName, 0, 2) - testEngine := metricsConf.NewMetricsEngine(cfg, adapterList) - // 2) Init new exchange with said configuration + syncerKeys := []string{} + testEngine := metricsConf.NewMetricsEngine(cfg, adapterList, syncerKeys) + // 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() @@ -1203,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) + e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} @@ -1557,7 +1559,7 @@ func TestBidResponseCurrency(t *testing.T) { } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - e := NewExchange(adapters, nil, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1696,7 +1698,7 @@ func TestBidResponseImpExtInfo(t *testing.T) { cfg := &config.Configuration{Adapters: make(map[string]config.Adapter, 1)} cfg.Adapters["appnexus"] = config.Adapter{Endpoint: "http://ib.adnxs.com"} - e := NewExchange(nil, nil, cfg, &metricsConf.DummyMetricsEngine{}, nil, gdpr.AlwaysAllow{}, nil, nilCategoryFetcher{}).(*exchange) + e := NewExchange(nil, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, nil, gdpr.AlwaysAllow{}, nil, nilCategoryFetcher{}).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1795,7 +1797,7 @@ func TestRaceIntegration(t *testing.T) { } debugLog := DebugLog{} - ex := NewExchange(adapters, &wellBehavedCache{}, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, &nilCategoryFetcher{}).(*exchange) + ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, &nilCategoryFetcher{}).(*exchange) _, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) @@ -1887,7 +1889,7 @@ func TestPanicRecovery(t *testing.T) { } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - e := NewExchange(adapters, nil, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) chBids := make(chan *bidResponseWrapper, 1) panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) { @@ -1960,7 +1962,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - e := NewExchange(adapters, &mockCache{}, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, categoriesFetcher).(*exchange) + e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, categoriesFetcher).(*exchange) e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} @@ -2256,6 +2258,11 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] t.Fatalf("Failed to create a category Fetcher: %v", error) } + bidderToSyncerKey := map[string]string{} + for _, bidderName := range openrtb_ext.CoreBidderNames() { + bidderToSyncerKey[string(bidderName)] = string(bidderName) + } + gdprDefaultValue := gdpr.SignalYes if privacyConfig.GDPR.DefaultValue == "0" { gdprDefaultValue = gdpr.SignalNo @@ -2263,7 +2270,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] return &exchange{ adapterMap: bidderAdapters, - me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), + me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil), cache: &wellBehavedCache{}, cacheTime: 0, gDPR: &permissionsMock{allowAllBidders: true}, @@ -2272,6 +2279,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] privacyConfig: privacyConfig, categoriesFetcher: categoriesFetcher, bidderInfo: bidderInfos, + bidderToSyncerKey: bidderToSyncerKey, externalURL: "http://localhost", bidIDGenerator: bidIDGenerator, } @@ -3635,13 +3643,13 @@ type bidderBid struct { type mockIdFetcher map[string]string -func (f mockIdFetcher) GetId(bidder openrtb_ext.BidderName) (id string, ok bool) { - id, ok = f[string(bidder)] +func (f mockIdFetcher) GetUID(key string) (uid string, exists bool, notExpired bool) { + uid, exists = f[string(key)] return } -func (f mockIdFetcher) LiveSyncCount() int { - return len(f) +func (f mockIdFetcher) HasAnyLiveSyncs() bool { + return len(f) > 0 } type validatingBidder struct { @@ -3810,25 +3818,12 @@ func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) type emptyUsersync struct{} -func (e *emptyUsersync) GetId(bidder openrtb_ext.BidderName) (string, bool) { - return "", false -} - -func (e *emptyUsersync) LiveSyncCount() int { - return 0 -} - -type mockUsersync struct { - syncs map[string]string -} - -func (e *mockUsersync) GetId(bidder openrtb_ext.BidderName) (id string, exists bool) { - id, exists = e.syncs[string(bidder)] - return +func (e *emptyUsersync) GetUID(key string) (uid string, exists bool, notExpired bool) { + return "", false, false } -func (e *mockUsersync) LiveSyncCount() int { - return len(e.syncs) +func (e *emptyUsersync) HasAnyLiveSyncs() bool { + return false } type panicingAdapter struct{} diff --git a/exchange/utils.go b/exchange/utils.go index 120152466a8..f33f1b9199c 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -53,6 +53,7 @@ func BidderToPrebidSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) (map[s func cleanOpenRTBRequests(ctx context.Context, req AuctionRequest, requestExt *openrtb_ext.ExtRequest, + bidderToSyncerKey map[string]string, gDPR gdpr.Permissions, metricsEngine metrics.MetricsEngine, gdprDefaultValue gdpr.Signal, @@ -71,7 +72,7 @@ func cleanOpenRTBRequests(ctx context.Context, } var allBidderRequests []BidderRequest - allBidderRequests, errs = getAuctionBidderRequests(req, requestExt, impsByBidder, aliases) + allBidderRequests, errs = getAuctionBidderRequests(req, requestExt, bidderToSyncerKey, impsByBidder, aliases) if len(allBidderRequests) == 0 { return @@ -204,6 +205,7 @@ func extractLMT(orig *openrtb2.BidRequest, privacyConfig config.Privacy) privacy func getAuctionBidderRequests(req AuctionRequest, requestExt *openrtb_ext.ExtRequest, + bidderToSyncerKey map[string]string, impsByBidder map[string][]openrtb2.Imp, aliases map[string]string) ([]BidderRequest, []error) { @@ -258,7 +260,8 @@ func getAuctionBidderRequests(req AuctionRequest, }, } - if hadSync := prepareUser(&reqCopy, bidder, coreBidder, explicitBuyerUIDs, req.UserSyncs); !hadSync && req.BidRequest.App == nil { + syncerKey := bidderToSyncerKey[string(coreBidder)] + if hadSync := prepareUser(&reqCopy, bidder, syncerKey, explicitBuyerUIDs, req.UserSyncs); !hadSync && req.BidRequest.App == nil { bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagNo } else { bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagYes @@ -459,8 +462,8 @@ func isSpecialField(bidder string) bool { // // In this function, "givenBidder" may or may not be an alias. "coreBidder" must *not* be an alias. // It returns true if a Cookie User Sync existed, and false otherwise. -func prepareUser(req *openrtb2.BidRequest, givenBidder string, coreBidder openrtb_ext.BidderName, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { - cookieId, hadCookie := usersyncs.GetId(coreBidder) +func prepareUser(req *openrtb2.BidRequest, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { + cookieId, hadCookie, _ := usersyncs.GetUID(syncerKey) if id, ok := explicitBuyerUIDs[givenBidder]; ok { req.User = copyWithBuyerUID(req.User, id) diff --git a/exchange/utils_test.go b/exchange/utils_test.go index a0812548ed5..81b0b9bd3c0 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -478,8 +478,9 @@ func TestCleanOpenRTBRequests(t *testing.T) { for _, test := range testCases { metricsMock := metrics.MetricsEngineMock{} + bidderToSyncerKey := map[string]string{} permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, gdpr.SignalNo, privacyConfig, nil) + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &permissions, &metricsMock, gdpr.SignalNo, privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -630,10 +631,12 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { Account: accountConfig, } + bidderToSyncerKey := map[string]string{} bidderRequests, privacyLabels, errs := cleanOpenRTBRequests( context.Background(), auctionReq, nil, + bidderToSyncerKey, &permissionsMock{allowAllBidders: true, passGeo: true, passID: true}, &metrics.MetricsEngineMock{}, gdpr.SignalNo, @@ -696,9 +699,10 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { Enforce: true, }, } + bidderToSyncerKey := map[string]string{} permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} metrics := metrics.MetricsEngineMock{} - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -738,9 +742,10 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { UserSyncs: &emptyUsersync{}, } + bidderToSyncerKey := map[string]string{} 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) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -847,9 +852,10 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { UserSyncs: &emptyUsersync{}, } + bidderToSyncerKey := map[string]string{} 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) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1430,9 +1436,10 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { }, } + bidderToSyncerKey := map[string]string{} 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) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1639,6 +1646,8 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { Account: accountConfig, } + bidderToSyncerKey := map[string]string{} + gdprDefaultValue := gdpr.SignalYes if test.gdprDefaultValue == "0" { gdprDefaultValue = gdpr.SignalNo @@ -1648,6 +1657,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { context.Background(), auctionReq, nil, + bidderToSyncerKey, &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, &metrics.MetricsEngineMock{}, gdprDefaultValue, @@ -1735,10 +1745,12 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { metricsMock := metrics.MetricsEngineMock{} metricsMock.Mock.On("RecordAdapterGDPRRequestBlocked", mock.Anything).Return() + bidderToSyncerKey := map[string]string{} results, _, errs := cleanOpenRTBRequests( context.Background(), auctionReq, nil, + bidderToSyncerKey, &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, &metricsMock, gdpr.SignalNo, diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index d2d282b2fec..46f4934d626 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -3,12 +3,10 @@ package gdpr import ( "context" "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" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -79,28 +77,10 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ // An ErrorMalformedConsent will be returned by the Permissions interface if // the consent string argument was the reason for the failure. type ErrorMalformedConsent struct { - consent string - cause error + Consent string + Cause error } func (e *ErrorMalformedConsent) Error() string { - return "malformed consent string " + e.consent + ": " + e.cause.Error() -} - -// SignalParse parses a raw signal and returns -func SignalParse(rawSignal string) (Signal, error) { - if rawSignal == "" { - return SignalAmbiguous, nil - } - - i, err := strconv.Atoi(rawSignal) - - if err != nil { - return SignalAmbiguous, err - } - if i != 0 && i != 1 { - return SignalAmbiguous, &errortypes.BadInput{Message: "GDPR signal should be integer 0 or 1"} - } - - return Signal(i), nil + return "malformed consent string " + e.Consent + ": " + e.Cause.Error() } diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go index 5048cf118f5..9e4f0940f03 100644 --- a/gdpr/gdpr_test.go +++ b/gdpr/gdpr_test.go @@ -7,7 +7,6 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" ) @@ -48,55 +47,3 @@ func TestNewPermissions(t *testing.T) { assert.IsType(t, tt.wantType, perms, tt.description) } } - -func TestSignalParse(t *testing.T) { - tests := []struct { - description string - rawSignal string - wantSignal Signal - wantError bool - }{ - { - description: "valid raw signal is 0", - rawSignal: "0", - wantSignal: SignalNo, - wantError: false, - }, - { - description: "Valid signal - raw signal is 1", - rawSignal: "1", - wantSignal: SignalYes, - wantError: false, - }, - { - description: "Valid signal - raw signal is empty", - rawSignal: "", - wantSignal: SignalAmbiguous, - wantError: false, - }, - { - description: "Invalid signal - raw signal is -1", - rawSignal: "-1", - wantSignal: SignalAmbiguous, - wantError: true, - }, - { - description: "Invalid signal - raw signal is abc", - rawSignal: "abc", - wantSignal: SignalAmbiguous, - wantError: true, - }, - } - - for _, tt := range tests { - signal, err := SignalParse(tt.rawSignal) - - assert.Equal(t, tt.wantSignal, signal, tt.description) - - if tt.wantError { - assert.NotNil(t, err, tt.description) - } else { - assert.Nil(t, err, tt.description) - } - } -} diff --git a/gdpr/impl.go b/gdpr/impl.go index 5f7e3e73fe2..9fb55462725 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -2,6 +2,7 @@ package gdpr import ( "context" + "errors" "fmt" "github.com/prebid/go-gdpr/api" @@ -14,19 +15,6 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -// This file implements GDPR permissions for the app. -// For more info, see https://github.com/prebid/prebid-server/issues/501 -// -// Nothing in this file is exported. Public APIs can be found in gdpr.go - -type Signal int - -const ( - SignalAmbiguous Signal = -1 - SignalNo Signal = 0 - SignalYes Signal = 1 -) - type permissionsImpl struct { cfg config.GDPR gdprDefaultValue Signal @@ -36,7 +24,7 @@ type permissionsImpl struct { } func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { - gdprSignal = p.normalizeGDPR(gdprSignal) + gdprSignal = SignalNormalize(gdprSignal, p.cfg) if gdprSignal == SignalNo { return true, nil @@ -46,7 +34,7 @@ func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Sig } func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { - gdprSignal = p.normalizeGDPR(gdprSignal) + gdprSignal = SignalNormalize(gdprSignal, p.cfg) if gdprSignal == SignalNo { return true, nil @@ -71,7 +59,7 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, return true, true, true, nil } - gdprSignal = p.normalizeGDPR(gdprSignal) + gdprSignal = SignalNormalize(gdprSignal, p.cfg) if gdprSignal == SignalNo { return true, true, true, nil @@ -94,20 +82,7 @@ func (p *permissionsImpl) defaultVendorPermissions() (allowBidRequest bool, pass return false, false, false, nil } -func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { - if gdprSignal != SignalAmbiguous { - return gdprSignal - } - - if p.gdprDefaultValue == SignalNo { - return SignalNo - } - - return SignalYes -} - func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string, vendorException bool) (bool, error) { - if consent == "" { return false, nil } @@ -126,7 +101,7 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen } consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) if !ok { - err := fmt.Errorf("Unable to access TCF2 parsed consent") + err := errors.New("Unable to access TCF2 parsed consent") return false, err } return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, vendorException, false), nil @@ -260,8 +235,8 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons parsedConsent, err = vendorconsent.ParseString(consent) if err != nil { err = &ErrorMalformedConsent{ - consent: consent, - cause: err, + Consent: consent, + Cause: err, } return } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index f7d90f3673b..f9da82a80a5 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -6,12 +6,11 @@ import ( "fmt" "testing" - "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" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -651,15 +650,6 @@ func TestProhibitedVendorSync(t *testing.T) { assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } -func parseVendorListData(t *testing.T, data string) vendorlist.VendorList { - t.Helper() - parsed, err := vendorlist.ParseEagerly([]byte(data)) - if err != nil { - t.Fatalf("Failed to parse vendor list data. %v", err) - } - return parsed -} - func parseVendorListDataV2(t *testing.T, data string) vendorlist.VendorList { t.Helper() parsed, err := vendorlist2.ParseEagerly([]byte(data)) @@ -708,77 +698,6 @@ func assertBoolsEqual(t *testing.T, expected bool, actual bool) { } } -func assertStringsEqual(t *testing.T, expected string, actual string) { - t.Helper() - if expected != actual { - t.Errorf("Expected %s, got %s", expected, actual) - } -} - -func TestNormalizeGDPR(t *testing.T) { - tests := []struct { - description string - gdprDefaultValue string - giveSignal Signal - wantSignal Signal - }{ - { - description: "Don't normalize - Signal No and gdprDefaultValue 1", - gdprDefaultValue: "1", - 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 gdprDefaultValue 1", - gdprDefaultValue: "1", - giveSignal: SignalYes, - wantSignal: SignalYes, - }, - { - description: "Don't normalize - Signal Yes and gdprDefaultValue 0", - gdprDefaultValue: "0", - giveSignal: SignalYes, - wantSignal: SignalYes, - }, - { - description: "Normalize - Signal Ambiguous and gdprDefaultValue 1", - gdprDefaultValue: "1", - giveSignal: SignalAmbiguous, - wantSignal: SignalYes, - }, - { - description: "Normalize - Signal Ambiguous and gdprDefaultValue 0", - gdprDefaultValue: "0", - giveSignal: SignalAmbiguous, - wantSignal: SignalNo, - }, - } - - for _, tt := range tests { - perms := permissionsImpl{ - cfg: config.GDPR{ - 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" diff --git a/gdpr/signal.go b/gdpr/signal.go new file mode 100644 index 00000000000..9487b48a0b7 --- /dev/null +++ b/gdpr/signal.go @@ -0,0 +1,46 @@ +package gdpr + +import ( + "strconv" + + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" +) + +type Signal int + +const ( + SignalAmbiguous Signal = -1 + SignalNo Signal = 0 + SignalYes Signal = 1 +) + +var gdprSignalError = &errortypes.BadInput{Message: "GDPR signal should be integer 0 or 1"} + +// SignalParse returns a parsed GDPR signal or a parse error. +func SignalParse(rawSignal string) (Signal, error) { + if rawSignal == "" { + return SignalAmbiguous, nil + } + + i, err := strconv.Atoi(rawSignal) + + if err != nil || (i != 0 && i != 1) { + return SignalAmbiguous, gdprSignalError + } + + return Signal(i), nil +} + +// SignalNormalize normalizes a GDPR signal to ensure it's always either SignalYes or SignalNo. +func SignalNormalize(signal Signal, config config.GDPR) Signal { + if signal != SignalAmbiguous { + return signal + } + + if config.DefaultValue == "0" { + return SignalNo + } + + return SignalYes +} diff --git a/gdpr/signal_test.go b/gdpr/signal_test.go new file mode 100644 index 00000000000..1b7e4e81bcd --- /dev/null +++ b/gdpr/signal_test.go @@ -0,0 +1,116 @@ +package gdpr + +import ( + "testing" + + "github.com/prebid/prebid-server/config" + "github.com/stretchr/testify/assert" +) + +func TestSignalParse(t *testing.T) { + tests := []struct { + description string + rawSignal string + wantSignal Signal + wantError bool + }{ + { + description: "valid raw signal is 0", + rawSignal: "0", + wantSignal: SignalNo, + wantError: false, + }, + { + description: "Valid signal - raw signal is 1", + rawSignal: "1", + wantSignal: SignalYes, + wantError: false, + }, + { + description: "Valid signal - raw signal is empty", + rawSignal: "", + wantSignal: SignalAmbiguous, + wantError: false, + }, + { + description: "Invalid signal - raw signal is -1", + rawSignal: "-1", + wantSignal: SignalAmbiguous, + wantError: true, + }, + { + description: "Invalid signal - raw signal is abc", + rawSignal: "abc", + wantSignal: SignalAmbiguous, + wantError: true, + }, + } + + for _, test := range tests { + signal, err := SignalParse(test.rawSignal) + + assert.Equal(t, test.wantSignal, signal, test.description) + + if test.wantError { + assert.NotNil(t, err, test.description) + } else { + assert.Nil(t, err, test.description) + } + } +} + +func TestSignalNormalize(t *testing.T) { + tests := []struct { + description string + defaultValue string + giveSignal Signal + wantSignal Signal + }{ + { + description: "Don't normalize - Signal No and Default Value 1", + defaultValue: "1", + giveSignal: SignalNo, + wantSignal: SignalNo, + }, + { + description: "Don't normalize - Signal No and Default Value 0", + defaultValue: "0", + giveSignal: SignalNo, + wantSignal: SignalNo, + }, + { + description: "Don't normalize - Signal Yes and Default Value 1", + defaultValue: "1", + giveSignal: SignalYes, + wantSignal: SignalYes, + }, + { + description: "Don't normalize - Signal Yes and Default Value 0", + defaultValue: "0", + giveSignal: SignalYes, + wantSignal: SignalYes, + }, + { + description: "Normalize - Signal Ambiguous and Default Value 1", + defaultValue: "1", + giveSignal: SignalAmbiguous, + wantSignal: SignalYes, + }, + { + description: "Normalize - Signal Ambiguous and Default Value 0", + defaultValue: "0", + giveSignal: SignalAmbiguous, + wantSignal: SignalNo, + }, + } + + for _, test := range tests { + config := config.GDPR{ + DefaultValue: test.defaultValue, + } + + normalizedSignal := SignalNormalize(test.giveSignal, config) + + assert.Equal(t, test.wantSignal, normalizedSignal, test.description) + } +} diff --git a/macros/macros.go b/macros/macros.go index 5d6bd7af65e..4440f2e242d 100644 --- a/macros/macros.go +++ b/macros/macros.go @@ -23,7 +23,7 @@ type UserSyncTemplateParams struct { } // ResolveMacros resolves macros in the given template with the provided params -func ResolveMacros(aTemplate template.Template, params interface{}) (string, error) { +func ResolveMacros(aTemplate *template.Template, params interface{}) (string, error) { strBuf := bytes.Buffer{} err := aTemplate.Execute(&strBuf, params) diff --git a/macros/macros_test.go b/macros/macros_test.go index cd8c4b557b9..055624b611f 100644 --- a/macros/macros_test.go +++ b/macros/macros_test.go @@ -10,27 +10,37 @@ import ( const validEndpointTemplate = "http://{{.Host}}/publisher/{{.PublisherID}}" func TestResolveMacros(t *testing.T) { - endpointTemplate, _ := template.New("endpointTemplate").Parse(validEndpointTemplate) + endpointTemplate := template.Must(template.New("endpointTemplate").Parse(validEndpointTemplate)) testCases := []struct { - aTemplate template.Template - params interface{} - result string - hasError bool + givenTemplate *template.Template + givenParams interface{} + expectedResult string + expectedError bool }{ - {aTemplate: *endpointTemplate, params: EndpointTemplateParams{Host: "SomeHost", PublisherID: "1"}, result: "http://SomeHost/publisher/1", hasError: false}, - {aTemplate: *endpointTemplate, params: UserSyncTemplateParams{GDPR: "SomeGDPR", GDPRConsent: "SomeGDPRConsent"}, result: "", hasError: true}, + { + givenTemplate: endpointTemplate, + givenParams: EndpointTemplateParams{Host: "SomeHost", PublisherID: "1"}, + expectedResult: "http://SomeHost/publisher/1", + expectedError: false, + }, + { + givenTemplate: endpointTemplate, + givenParams: UserSyncTemplateParams{GDPR: "SomeGDPR", GDPRConsent: "SomeGDPRConsent"}, + expectedResult: "", + expectedError: true, + }, } for _, test := range testCases { - res, err := ResolveMacros(test.aTemplate, test.params) + result, err := ResolveMacros(test.givenTemplate, test.givenParams) - if test.hasError { + if test.expectedError { assert.NotNil(t, err, "Error shouldn't be nil") - assert.Empty(t, res, "Result should be empty") + assert.Empty(t, result, "Result should be empty") } else { assert.Nil(t, err, "Err should be nil") - assert.Equal(t, res, test.result, "String after resolving macros should be %s", test.result) + assert.Equal(t, result, test.expectedResult, "String after resolving macros should be %s", test.expectedResult) } } } diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 2c65a475b8e..34d874d9553 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -13,7 +13,7 @@ import ( // NewMetricsEngine reads the configuration and returns the appropriate metrics engine // for this instance. -func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.BidderName) *DetailedMetricsEngine { +func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.BidderName, syncerKeys []string) *DetailedMetricsEngine { // Create a list of metrics engines to use. // Capacity of 2, as unlikely to have more than 2 metrics backends, and in the case // of 1 we won't use the list so it will be garbage collected. @@ -22,7 +22,7 @@ func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.Bidde if cfg.Metrics.Influxdb.Host != "" { // Currently use go-metrics as the metrics piece for influx - returnEngine.GoMetrics = metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, cfg.Metrics.Disabled) + returnEngine.GoMetrics = metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, cfg.Metrics.Disabled, syncerKeys) engineList = append(engineList, returnEngine.GoMetrics) // Set up the Influx logger go influxdb.InfluxDB( @@ -37,7 +37,7 @@ func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.Bidde } if cfg.Metrics.Prometheus.Port != 0 { // Set up the Prometheus metrics. - returnEngine.PrometheusMetrics = prometheusmetrics.NewMetrics(cfg.Metrics.Prometheus, cfg.Metrics.Disabled) + returnEngine.PrometheusMetrics = prometheusmetrics.NewMetrics(cfg.Metrics.Prometheus, cfg.Metrics.Disabled, syncerKeys) engineList = append(engineList, returnEngine.PrometheusMetrics) } @@ -175,9 +175,30 @@ func (me *MultiMetricsEngine) RecordAdapterTime(labels metrics.AdapterLabels, le } // RecordCookieSync across all engines -func (me *MultiMetricsEngine) RecordCookieSync() { +func (me *MultiMetricsEngine) RecordCookieSync(status metrics.CookieSyncStatus) { for _, thisME := range *me { - thisME.RecordCookieSync() + thisME.RecordCookieSync(status) + } +} + +// RecordSyncerRequest across all engines +func (me *MultiMetricsEngine) RecordSyncerRequest(key string, status metrics.SyncerCookieSyncStatus) { + for _, thisME := range *me { + thisME.RecordSyncerRequest(key, status) + } +} + +// RecordSetUid across all engines +func (me *MultiMetricsEngine) RecordSetUid(status metrics.SetUidStatus) { + for _, thisME := range *me { + thisME.RecordSetUid(status) + } +} + +// RecordSyncerSet across all engines +func (me *MultiMetricsEngine) RecordSyncerSet(key string, status metrics.SyncerSetUidStatus) { + for _, thisME := range *me { + thisME.RecordSyncerSet(key, status) } } @@ -202,20 +223,6 @@ func (me *MultiMetricsEngine) RecordAccountCacheResult(cacheResult metrics.Cache } } -// RecordAdapterCookieSync across all engines -func (me *MultiMetricsEngine) RecordAdapterCookieSync(adapter openrtb_ext.BidderName, gdprBlocked bool) { - for _, thisME := range *me { - thisME.RecordAdapterCookieSync(adapter, gdprBlocked) - } -} - -// RecordUserIDSet across all engines -func (me *MultiMetricsEngine) RecordUserIDSet(userLabels metrics.UserLabels) { - for _, thisME := range *me { - thisME.RecordUserIDSet(userLabels) - } -} - // RecordPrebidCacheRequestTime across all engines func (me *MultiMetricsEngine) RecordPrebidCacheRequestTime(success bool, length time.Duration) { for _, thisME := range *me { @@ -319,15 +326,19 @@ func (me *DummyMetricsEngine) RecordAdapterTime(labels metrics.AdapterLabels, le } // RecordCookieSync as a noop -func (me *DummyMetricsEngine) RecordCookieSync() { +func (me *DummyMetricsEngine) RecordCookieSync(status metrics.CookieSyncStatus) { +} + +// RecordSyncerRequest as a noop +func (me *DummyMetricsEngine) RecordSyncerRequest(key string, status metrics.SyncerCookieSyncStatus) { } -// RecordAdapterCookieSync as a noop -func (me *DummyMetricsEngine) RecordAdapterCookieSync(adapter openrtb_ext.BidderName, gdprBlocked bool) { +// RecordSetUid as a noop +func (me *DummyMetricsEngine) RecordSetUid(status metrics.SetUidStatus) { } -// RecordUserIDSet as a noop -func (me *DummyMetricsEngine) RecordUserIDSet(userLabels metrics.UserLabels) { +// RecordSyncerSet as a noop +func (me *DummyMetricsEngine) RecordSyncerSet(key string, status metrics.SyncerSetUidStatus) { } // RecordStoredReqCacheResult as a noop diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 7f9643330e3..04c888679e7 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -7,6 +7,7 @@ import ( mainConfig "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" + gometrics "github.com/rcrowley/go-metrics" ) @@ -14,7 +15,8 @@ import ( func TestDummyMetricsEngine(t *testing.T) { cfg := mainConfig.Configuration{} adapterList := make([]openrtb_ext.BidderName, 0, 2) - testEngine := NewMetricsEngine(&cfg, adapterList) + syncerKeys := []string{"keyA", "keyB"} + testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys) _, ok := testEngine.MetricsEngine.(*DummyMetricsEngine) if !ok { t.Error("Expected a DummyMetricsEngine, but didn't get it") @@ -25,7 +27,8 @@ func TestGoMetricsEngine(t *testing.T) { cfg := mainConfig.Configuration{} cfg.Metrics.Influxdb.Host = "localhost" adapterList := make([]openrtb_ext.BidderName, 0, 2) - testEngine := NewMetricsEngine(&cfg, adapterList) + syncerKeys := []string{"keyA", "keyB"} + testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys) _, ok := testEngine.MetricsEngine.(*metrics.Metrics) if !ok { t.Error("Expected a legacy Metrics as MetricsEngine, but didn't get it") @@ -37,7 +40,7 @@ func TestMultiMetricsEngine(t *testing.T) { cfg := mainConfig.Configuration{} cfg.Metrics.Influxdb.Host = "localhost" adapterList := openrtb_ext.CoreBidderNames() - goEngine := metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, mainConfig.DisabledMetrics{}) + goEngine := metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, mainConfig.DisabledMetrics{}, nil) engineList := make(MultiMetricsEngine, 2) engineList[0] = goEngine engineList[1] = &DummyMetricsEngine{} diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 93659db3fa7..615b83b8be9 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -38,12 +38,11 @@ type Metrics struct { RequestStatuses map[RequestType]map[RequestStatus]metrics.Meter AmpNoCookieMeter metrics.Meter CookieSyncMeter metrics.Meter - CookieSyncGen map[openrtb_ext.BidderName]metrics.Meter - CookieSyncGDPRPrevent map[openrtb_ext.BidderName]metrics.Meter - userSyncOptout metrics.Meter - userSyncBadRequest metrics.Meter - userSyncSet map[openrtb_ext.BidderName]metrics.Meter - userSyncGDPRPrevent map[openrtb_ext.BidderName]metrics.Meter + CookieSyncStatusMeter map[CookieSyncStatus]metrics.Meter + SyncerRequestsMeter map[string]map[SyncerCookieSyncStatus]metrics.Meter + SetUidMeter metrics.Meter + SetUidStatusMeter map[SetUidStatus]metrics.Meter + SyncerSetsMeter map[string]map[SyncerSetUidStatus]metrics.Meter // Media types found in the "imp" JSON object ImpsTypeBanner metrics.Meter @@ -66,7 +65,6 @@ type Metrics struct { // Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically accountMetrics map[string]*accountMetrics accountMetricsRWMutex sync.RWMutex - userSyncRwMutex sync.RWMutex exchanges []openrtb_ext.BidderName // Will hold boolean values to help us disable metric collection if needed @@ -140,12 +138,11 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa AccountCacheMeter: make(map[CacheResult]metrics.Meter), AmpNoCookieMeter: blankMeter, CookieSyncMeter: blankMeter, - CookieSyncGen: make(map[openrtb_ext.BidderName]metrics.Meter), - CookieSyncGDPRPrevent: make(map[openrtb_ext.BidderName]metrics.Meter), - userSyncOptout: blankMeter, - userSyncBadRequest: blankMeter, - userSyncSet: make(map[openrtb_ext.BidderName]metrics.Meter), - userSyncGDPRPrevent: make(map[openrtb_ext.BidderName]metrics.Meter), + CookieSyncStatusMeter: make(map[CookieSyncStatus]metrics.Meter), + SyncerRequestsMeter: make(map[string]map[SyncerCookieSyncStatus]metrics.Meter), + SetUidMeter: blankMeter, + SetUidStatusMeter: make(map[SetUidStatus]metrics.Meter), + SyncerSetsMeter: make(map[string]map[SyncerSetUidStatus]metrics.Meter), ImpsTypeBanner: blankMeter, ImpsTypeVideo: blankMeter, @@ -213,7 +210,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa // metrics object to contain only the metrics we are interested in. This would allow for debug // mode metrics. The code would allways try to record the metrics, but effectively noop if we are // using a blank meter/timer. -func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disableAccountMetrics config.DisabledMetrics) *Metrics { +func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, disableAccountMetrics config.DisabledMetrics, syncerKeys []string) *Metrics { newMetrics := NewBlankMetrics(registry, exchanges, disableAccountMetrics) newMetrics.ConnectionCounter = metrics.GetOrRegisterCounter("active_connections", registry) newMetrics.ConnectionAcceptErrorMeter = metrics.GetOrRegisterMeter("connection_accept_errors", registry) @@ -246,16 +243,33 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d } newMetrics.AmpNoCookieMeter = metrics.GetOrRegisterMeter("amp_no_cookie_requests", registry) + newMetrics.CookieSyncMeter = metrics.GetOrRegisterMeter("cookie_sync_requests", registry) - newMetrics.userSyncBadRequest = metrics.GetOrRegisterMeter("usersync.bad_requests", registry) - newMetrics.userSyncOptout = metrics.GetOrRegisterMeter("usersync.opt_outs", registry) + for _, s := range CookieSyncStatuses() { + newMetrics.CookieSyncStatusMeter[s] = metrics.GetOrRegisterMeter(fmt.Sprintf("cookie_sync_requests.%s", s), registry) + } + + newMetrics.SetUidMeter = metrics.GetOrRegisterMeter("setuid_requests", registry) + for _, s := range SetUidStatuses() { + newMetrics.SetUidStatusMeter[s] = metrics.GetOrRegisterMeter(fmt.Sprintf("setuid_requests.%s", s), registry) + } + + for _, syncerKey := range syncerKeys { + newMetrics.SyncerRequestsMeter[syncerKey] = make(map[SyncerCookieSyncStatus]metrics.Meter) + for _, status := range SyncerRequestStatuses() { + newMetrics.SyncerRequestsMeter[syncerKey][status] = metrics.GetOrRegisterMeter(fmt.Sprintf("syncer.%s.request.%s", syncerKey, status), registry) + } + + newMetrics.SyncerSetsMeter[syncerKey] = make(map[SyncerSetUidStatus]metrics.Meter) + for _, status := range SyncerSetUidStatuses() { + newMetrics.SyncerSetsMeter[syncerKey][status] = metrics.GetOrRegisterMeter(fmt.Sprintf("syncer.%s.set.%s", syncerKey, status), registry) + } + } + for _, a := range exchanges { - newMetrics.CookieSyncGen[a] = metrics.GetOrRegisterMeter(fmt.Sprintf("cookie_sync.%s.gen", string(a)), registry) - newMetrics.CookieSyncGDPRPrevent[a] = metrics.GetOrRegisterMeter(fmt.Sprintf("cookie_sync.%s.gdpr_prevent", string(a)), registry) - newMetrics.userSyncSet[a] = metrics.GetOrRegisterMeter(fmt.Sprintf("usersync.%s.sets", string(a)), registry) - newMetrics.userSyncGDPRPrevent[a] = metrics.GetOrRegisterMeter(fmt.Sprintf("usersync.%s.gdpr_prevent", string(a)), registry) registerAdapterMetrics(registry, "adapter", string(a), newMetrics.AdapterMetrics[a]) } + for typ, statusMap := range newMetrics.RequestStatuses { for stat := range statusMap { statusMap[stat] = metrics.GetOrRegisterMeter("requests."+string(stat)+"."+string(typ), registry) @@ -271,9 +285,6 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.RequestsQueueTimer["video"][true] = metrics.GetOrRegisterTimer("queued_requests.video.accepted", registry) newMetrics.RequestsQueueTimer["video"][false] = metrics.GetOrRegisterTimer("queued_requests.video.rejected", registry) - newMetrics.userSyncSet[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.sets", registry) - newMetrics.userSyncGDPRPrevent[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.gdpr_prevent", registry) - newMetrics.TimeoutNotificationSuccess = metrics.GetOrRegisterMeter("timeout_notification.ok", registry) newMetrics.TimeoutNotificationFailure = metrics.GetOrRegisterMeter("timeout_notification.failed", registry) @@ -579,7 +590,6 @@ func (me *Metrics) RecordAdapterBidReceived(labels AdapterLabels, bidType openrt } else { glog.Errorf("bid/adm metrics map entry does not exist for type %s. This is a bug, and should be reported.", bidType) } - return } // RecordAdapterPrice implements a part of the MetricsEngine interface. Generates a histogram of winning bid prices @@ -613,29 +623,36 @@ func (me *Metrics) RecordAdapterTime(labels AdapterLabels, length time.Duration) } // RecordCookieSync implements a part of the MetricsEngine interface. Records a cookie sync request -func (me *Metrics) RecordCookieSync() { +func (me *Metrics) RecordCookieSync(status CookieSyncStatus) { me.CookieSyncMeter.Mark(1) + if meter, exists := me.CookieSyncStatusMeter[status]; exists { + meter.Mark(1) + } +} + +// RecordSyncerRequest implements a part of the MetricsEngine interface. Records a cookie sync syncer request and status +func (me *Metrics) RecordSyncerRequest(key string, status SyncerCookieSyncStatus) { + if keyMeter, exists := me.SyncerRequestsMeter[key]; exists { + if statusMeter, exists := keyMeter[status]; exists { + statusMeter.Mark(1) + } + } } -// RecordAdapterCookieSync implements a part of the MetricsEngine interface. Records a cookie sync adpter sync request and gdpr status -func (me *Metrics) RecordAdapterCookieSync(adapter openrtb_ext.BidderName, gdprBlocked bool) { - me.CookieSyncGen[adapter].Mark(1) - if gdprBlocked { - me.CookieSyncGDPRPrevent[adapter].Mark(1) +// RecordSetUid implements a part of the MetricsEngine interface. Records a set uid sync request +func (me *Metrics) RecordSetUid(status SetUidStatus) { + me.SetUidMeter.Mark(1) + if meter, exists := me.SetUidStatusMeter[status]; exists { + meter.Mark(1) } } -// RecordUserIDSet implements a part of the MetricsEngine interface. Records a cookie setuid request -func (me *Metrics) RecordUserIDSet(userLabels UserLabels) { - switch userLabels.Action { - case RequestActionOptOut: - me.userSyncOptout.Mark(1) - case RequestActionErr: - me.userSyncBadRequest.Mark(1) - case RequestActionSet: - doMark(userLabels.Bidder, me.userSyncSet) - case RequestActionGDPR: - doMark(userLabels.Bidder, me.userSyncGDPRPrevent) +// RecordSyncerSet implements a part of the MetricsEngine interface. Records a set uid sync request and status +func (me *Metrics) RecordSyncerSet(key string, status SyncerSetUidStatus) { + if keyMeter, exists := me.SyncerSetsMeter[key]; exists { + if statusMeter, exists := keyMeter[status]; exists { + statusMeter.Mark(1) + } } } @@ -680,7 +697,6 @@ func (me *Metrics) RecordTimeoutNotice(success bool) { } else { me.TimeoutNotificationFailure.Mark(1) } - return } func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) { @@ -706,7 +722,6 @@ func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) { if privacy.LMTEnforced { me.PrivacyLMTRequest.Mark(1) } - return } func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { @@ -722,12 +737,3 @@ func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.Bidde am.GDPRRequestBlocked.Mark(1) } - -func doMark(bidder openrtb_ext.BidderName, meters map[openrtb_ext.BidderName]metrics.Meter) { - met, ok := meters[bidder] - if ok { - met.Mark(1) - } else { - meters[unknownBidder].Mark(1) - } -} diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 61930bf54f0..346a64a737f 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -12,7 +12,8 @@ import ( func TestNewMetrics(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}) + syncerKeys := []string{"foo"} + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys) ensureContains(t, registry, "app_requests", m.AppRequestMeter) ensureContains(t, registry, "no_cookie_requests", m.NoCookieMeter) @@ -21,11 +22,17 @@ func TestNewMetrics(t *testing.T) { ensureContainsAdapterMetrics(t, registry, "adapter.appnexus", m.AdapterMetrics["appnexus"]) ensureContainsAdapterMetrics(t, registry, "adapter.rubicon", m.AdapterMetrics["rubicon"]) ensureContains(t, registry, "cookie_sync_requests", m.CookieSyncMeter) - ensureContains(t, registry, "cookie_sync.appnexus.gen", m.CookieSyncGen["appnexus"]) - ensureContains(t, registry, "cookie_sync.appnexus.gdpr_prevent", m.CookieSyncGDPRPrevent["appnexus"]) - ensureContains(t, registry, "usersync.appnexus.gdpr_prevent", m.userSyncGDPRPrevent["appnexus"]) - ensureContains(t, registry, "usersync.rubicon.gdpr_prevent", m.userSyncGDPRPrevent["rubicon"]) - ensureContains(t, registry, "usersync.unknown.gdpr_prevent", m.userSyncGDPRPrevent["unknown"]) + ensureContains(t, registry, "cookie_sync_requests.ok", m.CookieSyncStatusMeter[CookieSyncOK]) + ensureContains(t, registry, "cookie_sync_requests.bad_request", m.CookieSyncStatusMeter[CookieSyncBadRequest]) + ensureContains(t, registry, "cookie_sync_requests.opt_out", m.CookieSyncStatusMeter[CookieSyncOptOut]) + ensureContains(t, registry, "cookie_sync_requests.gdpr_blocked_host_cookie", m.CookieSyncStatusMeter[CookieSyncGDPRHostCookieBlocked]) + ensureContains(t, registry, "setuid_requests", m.SetUidMeter) + ensureContains(t, registry, "setuid_requests.ok", m.SetUidStatusMeter[SetUidOK]) + ensureContains(t, registry, "setuid_requests.bad_request", m.SetUidStatusMeter[SetUidBadRequest]) + ensureContains(t, registry, "setuid_requests.opt_out", m.SetUidStatusMeter[SetUidOptOut]) + ensureContains(t, registry, "setuid_requests.gdpr_blocked_host_cookie", m.SetUidStatusMeter[SetUidGDPRHostCookieBlocked]) + ensureContains(t, registry, "setuid_requests.syncer_unknown", m.SetUidStatusMeter[SetUidSyncerUnknown]) + ensureContains(t, registry, "prebid_cache_request_time.ok", m.PrebidCacheRequestTimerSuccess) ensureContains(t, registry, "prebid_cache_request_time.err", m.PrebidCacheRequestTimerError) @@ -62,11 +69,18 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "privacy.request.lmt", m.PrivacyLMTRequest) ensureContains(t, registry, "privacy.request.tcf.v2", m.PrivacyTCFRequestVersion[TCFVersionV2]) ensureContains(t, registry, "privacy.request.tcf.err", m.PrivacyTCFRequestVersion[TCFVersionErr]) + + ensureContains(t, registry, "syncer.foo.request.ok", m.SyncerRequestsMeter["foo"][SyncerCookieSyncOK]) + ensureContains(t, registry, "syncer.foo.request.privacy_blocked", m.SyncerRequestsMeter["foo"][SyncerCookieSyncPrivacyBlocked]) + ensureContains(t, registry, "syncer.foo.request.already_synced", m.SyncerRequestsMeter["foo"][SyncerCookieSyncAlreadySynced]) + ensureContains(t, registry, "syncer.foo.request.type_not_supported", m.SyncerRequestsMeter["foo"][SyncerCookieSyncTypeNotSupported]) + ensureContains(t, registry, "syncer.foo.set.ok", m.SyncerSetsMeter["foo"][SyncerSetUidOK]) + ensureContains(t, registry, "syncer.foo.set.cleared", m.SyncerSetsMeter["foo"][SyncerSetUidCleared]) } func TestRecordBidType(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}, nil) m.RecordAdapterBidReceived(AdapterLabels{ Adapter: openrtb_ext.BidderAppnexus, @@ -81,16 +95,6 @@ func TestRecordBidType(t *testing.T) { VerifyMetrics(t, "Appnexus Video Nurl Bids", m.AdapterMetrics[openrtb_ext.BidderAppnexus].MarkupMetrics[openrtb_ext.BidTypeVideo].NurlMeter.Count(), 1) } -func TestRecordGDPRRejection(t *testing.T) { - registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{}) - m.RecordUserIDSet(UserLabels{ - Action: RequestActionGDPR, - Bidder: openrtb_ext.BidderAppnexus, - }) - VerifyMetrics(t, "GDPR sync rejects", m.userSyncGDPRPrevent[openrtb_ext.BidderAppnexus].Count(), 1) -} - func ensureContains(t *testing.T, registry metrics.Registry, name string, metric interface{}) { t.Helper() if inRegistry := registry.Get(name); inRegistry == nil { @@ -164,7 +168,7 @@ func TestRecordBidTypeDisabledConfig(t *testing.T) { for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.DisabledMetrics) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, test.DisabledMetrics, nil) m.RecordAdapterBidReceived(AdapterLabels{ Adapter: openrtb_ext.BidderAppnexus, @@ -200,7 +204,7 @@ func TestRecordDNSTime(t *testing.T) { } for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil) m.RecordDNSTime(test.inDnsLookupDuration) @@ -227,7 +231,7 @@ func TestRecordTLSHandshakeTime(t *testing.T) { } for _, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil) m.RecordTLSHandshakeTime(test.tLSHandshakeDuration) @@ -332,7 +336,7 @@ func TestRecordAdapterConnections(t *testing.T) { for i, test := range testCases { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterConnectionMetrics: test.in.connMetricsDisabled}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterConnectionMetrics: test.in.connMetricsDisabled}, nil) m.RecordAdapterConnections(test.in.adapterName, test.in.connWasReused, test.in.connWait) @@ -344,14 +348,14 @@ func TestRecordAdapterConnections(t *testing.T) { func TestNewMetricsWithDisabledConfig(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil) assert.True(t, m.MetricsDisabled.AccountAdapterDetails, "Accound adapter metrics should be disabled") } func TestRecordPrebidCacheRequestTimeWithSuccess(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil) m.RecordPrebidCacheRequestTime(true, 42) @@ -361,7 +365,7 @@ func TestRecordPrebidCacheRequestTimeWithSuccess(t *testing.T) { func TestRecordPrebidCacheRequestTimeWithNotSuccess(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}, nil) m.RecordPrebidCacheRequestTime(false, 42) @@ -429,7 +433,7 @@ func TestRecordStoredDataFetchTime(t *testing.T) { for _, tt := range tests { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil) m.RecordStoredDataFetchTime(StoredDataLabels{ DataType: tt.dataType, DataFetchType: tt.fetchType, @@ -503,7 +507,7 @@ func TestRecordStoredDataError(t *testing.T) { for _, tt := range tests { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil) m.RecordStoredDataError(StoredDataLabels{ DataType: tt.dataType, Error: tt.errorType, @@ -516,7 +520,7 @@ func TestRecordStoredDataError(t *testing.T) { func TestRecordRequestPrivacy(t *testing.T) { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true}, nil) // CCPA m.RecordRequestPrivacy(PrivacyLabels{ @@ -591,7 +595,7 @@ func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { for _, tt := range tests { registry := metrics.NewRegistry() - m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}) + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}, nil) m.RecordAdapterGDPRRequestBlocked(tt.adapterName) @@ -599,6 +603,79 @@ func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { } } +func TestRecordCookieSync(t *testing.T) { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, nil) + + // Known + m.RecordCookieSync(CookieSyncBadRequest) + + // Unknown + m.RecordCookieSync(CookieSyncStatus("unknown status")) + + assert.Equal(t, m.CookieSyncMeter.Count(), int64(2)) + assert.Equal(t, m.CookieSyncStatusMeter[CookieSyncOK].Count(), int64(0)) + assert.Equal(t, m.CookieSyncStatusMeter[CookieSyncBadRequest].Count(), int64(1)) + assert.Equal(t, m.CookieSyncStatusMeter[CookieSyncOptOut].Count(), int64(0)) + assert.Equal(t, m.CookieSyncStatusMeter[CookieSyncGDPRHostCookieBlocked].Count(), int64(0)) +} + +func TestRecordSyncerRequest(t *testing.T) { + registry := metrics.NewRegistry() + syncerKeys := []string{"foo"} + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys) + + // Known + m.RecordSyncerRequest("foo", SyncerCookieSyncOK) + + // Unknown Bidder + m.RecordSyncerRequest("bar", SyncerCookieSyncOK) + + // Unknown Status + m.RecordSyncerRequest("foo", SyncerCookieSyncStatus("unknown status")) + + assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncOK].Count(), int64(1)) + assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncPrivacyBlocked].Count(), int64(0)) + assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncAlreadySynced].Count(), int64(0)) + assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncTypeNotSupported].Count(), int64(0)) +} + +func TestRecordSetUid(t *testing.T) { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, nil) + + // Known + m.RecordSetUid(SetUidOptOut) + + // Unknown + m.RecordSetUid(SetUidStatus("unknown status")) + + assert.Equal(t, m.SetUidMeter.Count(), int64(2)) + assert.Equal(t, m.SetUidStatusMeter[SetUidOK].Count(), int64(0)) + assert.Equal(t, m.SetUidStatusMeter[SetUidBadRequest].Count(), int64(0)) + assert.Equal(t, m.SetUidStatusMeter[SetUidOptOut].Count(), int64(1)) + assert.Equal(t, m.SetUidStatusMeter[SetUidGDPRHostCookieBlocked].Count(), int64(0)) + assert.Equal(t, m.SetUidStatusMeter[SetUidSyncerUnknown].Count(), int64(0)) +} + +func TestRecordSyncerSet(t *testing.T) { + registry := metrics.NewRegistry() + syncerKeys := []string{"foo"} + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{}, syncerKeys) + + // Known + m.RecordSyncerSet("foo", SyncerSetUidCleared) + + // Unknown Bidder + m.RecordSyncerSet("bar", SyncerSetUidCleared) + + // Unknown Status + m.RecordSyncerSet("foo", SyncerSetUidStatus("unknown status")) + + assert.Equal(t, m.SyncerSetsMeter["foo"][SyncerSetUidOK].Count(), int64(0)) + assert.Equal(t, m.SyncerSetsMeter["foo"][SyncerSetUidCleared].Count(), int64(1)) +} + 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 5912a151bca..af45f9b4f5a 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -267,33 +267,6 @@ func CacheResults() []CacheResult { } } -// UserLabels : Labels for /setuid endpoint -type UserLabels struct { - Action RequestAction - Bidder openrtb_ext.BidderName -} - -// RequestAction : The setuid request result -type RequestAction string - -// /setuid action labels -const ( - RequestActionSet RequestAction = "set" - RequestActionOptOut RequestAction = "opt_out" - RequestActionGDPR RequestAction = "gdpr" - RequestActionErr RequestAction = "err" -) - -// RequestActions returns possible setuid action labels -func RequestActions() []RequestAction { - return []RequestAction{ - RequestActionSet, - RequestActionOptOut, - RequestActionGDPR, - RequestActionErr, - } -} - // TCFVersionValue : The possible values for TCF versions type TCFVersionValue string @@ -319,6 +292,85 @@ func TCFVersionToValue(version int) TCFVersionValue { return TCFVersionErr } +// CookieSyncStatus is a status code resulting from a call to the /cookie_sync endpoint. +type CookieSyncStatus string + +const ( + CookieSyncOK CookieSyncStatus = "ok" + CookieSyncBadRequest CookieSyncStatus = "bad_request" + CookieSyncOptOut CookieSyncStatus = "opt_out" + CookieSyncGDPRHostCookieBlocked CookieSyncStatus = "gdpr_blocked_host_cookie" +) + +// CookieSyncStatuses returns possible cookie sync statuses. +func CookieSyncStatuses() []CookieSyncStatus { + return []CookieSyncStatus{ + CookieSyncOK, + CookieSyncBadRequest, + CookieSyncOptOut, + CookieSyncGDPRHostCookieBlocked, + } +} + +// SyncerCookieSyncStatus is a status code from an invocation of a syncer resulting from a call to the /cookie_sync endpoint. +type SyncerCookieSyncStatus string + +const ( + SyncerCookieSyncOK SyncerCookieSyncStatus = "ok" + SyncerCookieSyncPrivacyBlocked SyncerCookieSyncStatus = "privacy_blocked" + SyncerCookieSyncAlreadySynced SyncerCookieSyncStatus = "already_synced" + SyncerCookieSyncTypeNotSupported SyncerCookieSyncStatus = "type_not_supported" +) + +// SyncerRequestStatuses returns possible syncer statuses. +func SyncerRequestStatuses() []SyncerCookieSyncStatus { + return []SyncerCookieSyncStatus{ + SyncerCookieSyncOK, + SyncerCookieSyncPrivacyBlocked, + SyncerCookieSyncAlreadySynced, + SyncerCookieSyncTypeNotSupported, + } +} + +// SetUidStatus is a status code resulting from a call to the /setuid endpoint. +type SetUidStatus string + +// /setuid action labels +const ( + SetUidOK SetUidStatus = "ok" + SetUidBadRequest SetUidStatus = "bad_request" + SetUidOptOut SetUidStatus = "opt_out" + SetUidGDPRHostCookieBlocked SetUidStatus = "gdpr_blocked_host_cookie" + SetUidSyncerUnknown SetUidStatus = "syncer_unknown" +) + +// SetUidStatuses returns possible setuid statuses. +func SetUidStatuses() []SetUidStatus { + return []SetUidStatus{ + SetUidOK, + SetUidBadRequest, + SetUidOptOut, + SetUidGDPRHostCookieBlocked, + SetUidSyncerUnknown, + } +} + +// SyncerSetUidStatus is a status code from an invocation of a syncer resulting from a call to the /setuid endpoint. +type SyncerSetUidStatus string + +const ( + SyncerSetUidOK SyncerSetUidStatus = "ok" + SyncerSetUidCleared SyncerSetUidStatus = "cleared" +) + +// SyncerSetUidStatuses returns possible syncer set statuses. +func SyncerSetUidStatuses() []SyncerSetUidStatus { + return []SyncerSetUidStatus{ + SyncerSetUidOK, + SyncerSetUidCleared, + } +} + // MetricsEngine is a generic interface to record PBS metrics into the desired backend // The first three metrics function fire off once per incoming request, so total metrics // will equal the total number of incoming requests. The remaining 5 fire off per outgoing @@ -342,9 +394,10 @@ type MetricsEngine interface { RecordAdapterBidReceived(labels AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) RecordAdapterPrice(labels AdapterLabels, cpm float64) RecordAdapterTime(labels AdapterLabels, length time.Duration) - RecordCookieSync() - RecordAdapterCookieSync(adapter openrtb_ext.BidderName, gdprBlocked bool) - RecordUserIDSet(userLabels UserLabels) // Function should verify bidder values + RecordCookieSync(status CookieSyncStatus) + RecordSyncerRequest(key string, status SyncerCookieSyncStatus) + RecordSetUid(status SetUidStatus) + RecordSyncerSet(key string, status SyncerSetUidStatus) RecordStoredReqCacheResult(cacheResult CacheResult, inc int) RecordStoredImpCacheResult(cacheResult CacheResult, inc int) RecordAccountCacheResult(cacheResult CacheResult, inc int) diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index 261002b3d4d..b8ab23b768a 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -92,18 +92,23 @@ func (me *MetricsEngineMock) RecordAdapterTime(labels AdapterLabels, length time } // RecordCookieSync mock -func (me *MetricsEngineMock) RecordCookieSync() { - me.Called() +func (me *MetricsEngineMock) RecordCookieSync(status CookieSyncStatus) { + me.Called(status) } -// RecordAdapterCookieSync mock -func (me *MetricsEngineMock) RecordAdapterCookieSync(adapter openrtb_ext.BidderName, gdprBlocked bool) { - me.Called(adapter, gdprBlocked) +// RecordSyncerRequest mock +func (me *MetricsEngineMock) RecordSyncerRequest(key string, status SyncerCookieSyncStatus) { + me.Called(key, status) } -// RecordUserIDSet mock -func (me *MetricsEngineMock) RecordUserIDSet(userLabels UserLabels) { - me.Called(userLabels) +// RecordSetUid mock +func (me *MetricsEngineMock) RecordSetUid(status SetUidStatus) { + me.Called(status) +} + +// RecordSyncerSet mock +func (me *MetricsEngineMock) RecordSyncerSet(key string, status SyncerSetUidStatus) { + me.Called(key, status) } // RecordStoredReqCacheResult mock diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index 621ad808619..338b1e8b347 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -5,9 +5,9 @@ import ( "github.com/prometheus/client_golang/prometheus" ) -func preloadLabelValues(m *Metrics) { +func preloadLabelValues(m *Metrics, syncerKeys []string) { var ( - actionValues = actionsAsString() + setUidStatusValues = setUidStatusesAsString() adapterErrorValues = adapterErrorsAsString() adapterValues = adaptersAsString() bidTypeValues = []string{markupDeliveryAdm, markupDeliveryNurl} @@ -15,10 +15,13 @@ func preloadLabelValues(m *Metrics) { cacheResultValues = cacheResultsAsString() connectionErrorValues = []string{connectionAcceptError, connectionCloseError} cookieValues = cookieTypesAsString() - requestStatusValues = requestStatusesAsString() + cookieSyncStatusValues = cookieSyncStatusesAsString() requestTypeValues = requestTypesAsString() + requestStatusValues = requestStatusesAsString() storedDataFetchTypeValues = storedDataFetchTypesAsString() storedDataErrorValues = storedDataErrorsAsString() + syncerRequestStatusValues = syncerRequestStatusesAsString() + syncerSetsStatusValues = syncerSetStatusesAsString() sourceValues = []string{sourceRequest} ) @@ -26,6 +29,14 @@ func preloadLabelValues(m *Metrics) { connectionErrorLabel: connectionErrorValues, }) + preloadLabelValuesForCounter(m.cookieSync, map[string][]string{ + statusLabel: cookieSyncStatusValues, + }) + + preloadLabelValuesForCounter(m.setUid, map[string][]string{ + statusLabel: setUidStatusValues, + }) + preloadLabelValuesForCounter(m.impressions, map[string][]string{ isBannerLabel: boolValues, isVideoLabel: boolValues, @@ -107,11 +118,6 @@ func preloadLabelValues(m *Metrics) { markupDeliveryLabel: bidTypeValues, }) - preloadLabelValuesForCounter(m.adapterCookieSync, map[string][]string{ - adapterLabel: adapterValues, - privacyBlockedLabel: boolValues, - }) - preloadLabelValuesForCounter(m.adapterErrors, map[string][]string{ adapterLabel: adapterValues, adapterErrorLabel: adapterErrorValues, @@ -149,9 +155,14 @@ func preloadLabelValues(m *Metrics) { adapterLabel: adapterValues, }) - preloadLabelValuesForCounter(m.adapterUserSync, map[string][]string{ - adapterLabel: adapterValues, - actionLabel: actionValues, + preloadLabelValuesForCounter(m.syncerRequests, map[string][]string{ + syncerLabel: syncerKeys, + statusLabel: syncerRequestStatusValues, + }) + + preloadLabelValuesForCounter(m.syncerSets, map[string][]string{ + syncerLabel: syncerKeys, + statusLabel: syncerSetsStatusValues, }) //to minimize memory usage, queuedTimeout metric is now supported for video endpoint only diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 2c7361d04cb..52470369094 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -18,7 +18,8 @@ type Metrics struct { connectionsClosed prometheus.Counter connectionsError *prometheus.CounterVec connectionsOpened prometheus.Counter - cookieSync prometheus.Counter + cookieSync *prometheus.CounterVec + setUid *prometheus.CounterVec impressions *prometheus.CounterVec impressionsLegacy prometheus.Counter prebidCacheWriteTimer *prometheus.HistogramVec @@ -49,18 +50,20 @@ type Metrics struct { // Adapter Metrics adapterBids *prometheus.CounterVec - adapterCookieSync *prometheus.CounterVec adapterErrors *prometheus.CounterVec adapterPanics *prometheus.CounterVec adapterPrices *prometheus.HistogramVec adapterRequests *prometheus.CounterVec adapterRequestsTimer *prometheus.HistogramVec - adapterUserSync *prometheus.CounterVec adapterReusedConnections *prometheus.CounterVec adapterCreatedConnections *prometheus.CounterVec adapterConnectionWaitTime *prometheus.HistogramVec adapterGDPRBlockedRequests *prometheus.CounterVec + // Syncer Metrics + syncerRequests *prometheus.CounterVec + syncerSets *prometheus.CounterVec + // Account Metrics accountRequests *prometheus.CounterVec @@ -86,7 +89,9 @@ const ( privacyBlockedLabel = "privacy_blocked" requestStatusLabel = "request_status" requestTypeLabel = "request_type" + statusLabel = "status" successLabel = "success" + syncerLabel = "syncer" versionLabel = "version" ) @@ -121,7 +126,7 @@ const ( ) // NewMetrics initializes a new Prometheus metrics instance with preloaded label values. -func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMetrics) *Metrics { +func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMetrics, syncerKeys []string) *Metrics { standardTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1} cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1} priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000} @@ -144,9 +149,15 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "connections_opened", "Count of successful connections opened to Prebid Server.") - metrics.cookieSync = newCounterWithoutLabels(cfg, metrics.Registry, + metrics.cookieSync = newCounter(cfg, metrics.Registry, "cookie_sync_requests", - "Count of cookie sync requests to Prebid Server.") + "Count of cookie sync requests to Prebid Server.", + []string{statusLabel}) + + metrics.setUid = newCounter(cfg, metrics.Registry, + "setuid_requests", + "Count of set uid requests to Prebid Server.", + []string{statusLabel}) metrics.impressions = newCounter(cfg, metrics.Registry, "impressions_requests", @@ -296,11 +307,6 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Count of bids labeled by adapter and markup delivery type (adm or nurl).", []string{adapterLabel, markupDeliveryLabel}) - metrics.adapterCookieSync = newCounter(cfg, metrics.Registry, - "adapter_cookie_sync", - "Count of cookie sync requests received labeled by adapter and if the sync was blocked due to privacy regulation (GDPR, CCPA, etc...).", - []string{adapterLabel, privacyBlockedLabel}) - metrics.adapterErrors = newCounter(cfg, metrics.Registry, "adapter_errors", "Count of errors labeled by adapter and error type.", @@ -346,10 +352,15 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet []string{adapterLabel}, standardTimeBuckets) - metrics.adapterUserSync = newCounter(cfg, metrics.Registry, - "adapter_user_sync", - "Count of user ID sync requests received labeled by adapter and action.", - []string{adapterLabel, actionLabel}) + metrics.syncerRequests = newCounter(cfg, metrics.Registry, + "syncer_requests", + "Count of cookie sync requests where a syncer is a candidate to be synced labeled by syncer key and status.", + []string{syncerLabel, statusLabel}) + + metrics.syncerSets = newCounter(cfg, metrics.Registry, + "syncer_sets", + "Count of setuid set requests for a syncer labeled by syncer key and status.", + []string{syncerLabel, statusLabel}) metrics.accountRequests = newCounter(cfg, metrics.Registry, "account_requests", @@ -362,7 +373,7 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet []string{requestTypeLabel, requestStatusLabel}, queuedRequestTimeBuckets) - preloadLabelValues(&metrics) + preloadLabelValues(&metrics, syncerKeys) return &metrics } @@ -604,25 +615,30 @@ func (m *Metrics) RecordAdapterTime(labels metrics.AdapterLabels, length time.Du } } -func (m *Metrics) RecordCookieSync() { - m.cookieSync.Inc() +func (m *Metrics) RecordCookieSync(status metrics.CookieSyncStatus) { + m.cookieSync.With(prometheus.Labels{ + statusLabel: string(status), + }).Inc() } -func (m *Metrics) RecordAdapterCookieSync(adapter openrtb_ext.BidderName, privacyBlocked bool) { - m.adapterCookieSync.With(prometheus.Labels{ - adapterLabel: string(adapter), - privacyBlockedLabel: strconv.FormatBool(privacyBlocked), +func (m *Metrics) RecordSyncerRequest(key string, status metrics.SyncerCookieSyncStatus) { + m.syncerRequests.With(prometheus.Labels{ + syncerLabel: key, + statusLabel: string(status), }).Inc() } -func (m *Metrics) RecordUserIDSet(labels metrics.UserLabels) { - adapter := string(labels.Bidder) - if adapter != "" { - m.adapterUserSync.With(prometheus.Labels{ - adapterLabel: adapter, - actionLabel: string(labels.Action), - }).Inc() - } +func (m *Metrics) RecordSetUid(status metrics.SetUidStatus) { + m.setUid.With(prometheus.Labels{ + statusLabel: string(status), + }).Inc() +} + +func (m *Metrics) RecordSyncerSet(key string, status metrics.SyncerSetUidStatus) { + m.syncerSets.With(prometheus.Labels{ + syncerLabel: key, + statusLabel: string(status), + }).Inc() } func (m *Metrics) RecordStoredReqCacheResult(cacheResult metrics.CacheResult, inc int) { diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 087ee570551..0fe852b81df 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -14,11 +14,12 @@ import ( ) func createMetricsForTesting() *Metrics { + syncerKeys := []string{} return NewMetrics(config.PrometheusMetrics{ Port: 8080, Namespace: "prebid", Subsystem: "server", - }, config.DisabledMetrics{}) + }, config.DisabledMetrics{}, syncerKeys) } func TestMetricCountGatekeeping(t *testing.T) { @@ -922,58 +923,6 @@ func TestAdapterTimeMetric(t *testing.T) { } } -func TestAdapterCookieSyncMetric(t *testing.T) { - m := createMetricsForTesting() - adapterName := "anyName" - privacyBlocked := true - - m.RecordAdapterCookieSync(openrtb_ext.BidderName(adapterName), privacyBlocked) - - expectedCount := float64(1) - assertCounterVecValue(t, "", "adapterCookieSync", m.adapterCookieSync, - expectedCount, - prometheus.Labels{ - adapterLabel: adapterName, - privacyBlockedLabel: "true", - }) -} - -func TestUserIDSetMetric(t *testing.T) { - m := createMetricsForTesting() - adapterName := "anyName" - action := metrics.RequestActionSet - - m.RecordUserIDSet(metrics.UserLabels{ - Bidder: openrtb_ext.BidderName(adapterName), - Action: action, - }) - - expectedCount := float64(1) - assertCounterVecValue(t, "", "adapterUserSync", m.adapterUserSync, - expectedCount, - prometheus.Labels{ - adapterLabel: adapterName, - actionLabel: string(action), - }) -} - -func TestUserIDSetMetricWhenBidderEmpty(t *testing.T) { - m := createMetricsForTesting() - action := metrics.RequestActionErr - - m.RecordUserIDSet(metrics.UserLabels{ - Bidder: openrtb_ext.BidderName(""), - Action: action, - }) - - expectedTotalCount := float64(0) - actualTotalCount := float64(0) - processMetrics(m.adapterUserSync, func(m dto.Metric) { - actualTotalCount += m.GetCounter().GetValue() - }) - assert.Equal(t, expectedTotalCount, actualTotalCount, "total count") -} - func TestAdapterPanicMetric(t *testing.T) { m := createMetricsForTesting() adapterName := "anyName" @@ -1050,14 +999,150 @@ func TestAccountCacheResultMetric(t *testing.T) { }) } -func TestCookieMetric(t *testing.T) { - m := createMetricsForTesting() +func TestCookieSyncMetric(t *testing.T) { + tests := []struct { + status metrics.CookieSyncStatus + label string + }{ + { + status: metrics.CookieSyncOK, + label: "ok", + }, + { + status: metrics.CookieSyncBadRequest, + label: "bad_request", + }, + { + status: metrics.CookieSyncOptOut, + label: "opt_out", + }, + { + status: metrics.CookieSyncGDPRHostCookieBlocked, + label: "gdpr_blocked_host_cookie", + }, + } - m.RecordCookieSync() + for _, test := range tests { + m := createMetricsForTesting() - expectedCount := float64(1) - assertCounterValue(t, "", "cookieSync", m.cookieSync, - expectedCount) + m.RecordCookieSync(test.status) + + assertCounterVecValue(t, "", "cookie_sync_requests:"+test.label, m.cookieSync, + float64(1), + prometheus.Labels{ + statusLabel: string(test.status), + }) + } +} + +func TestRecordSyncerRequestMetric(t *testing.T) { + key := "anyKey" + + tests := []struct { + status metrics.SyncerCookieSyncStatus + label string + }{ + { + status: metrics.SyncerCookieSyncOK, + label: "ok", + }, + { + status: metrics.SyncerCookieSyncPrivacyBlocked, + label: "privacy_blocked", + }, + { + status: metrics.SyncerCookieSyncAlreadySynced, + label: "already_synced", + }, + { + status: metrics.SyncerCookieSyncTypeNotSupported, + label: "type_not_supported", + }, + } + + for _, test := range tests { + m := createMetricsForTesting() + + m.RecordSyncerRequest(key, test.status) + + assertCounterVecValue(t, "", "syncer_requests:"+test.label, m.syncerRequests, + float64(1), + prometheus.Labels{ + syncerLabel: key, + statusLabel: string(test.status), + }) + } +} + +func TestSetUidMetric(t *testing.T) { + tests := []struct { + status metrics.SetUidStatus + label string + }{ + { + status: metrics.SetUidOK, + label: "ok", + }, + { + status: metrics.SetUidBadRequest, + label: "bad_request", + }, + { + status: metrics.SetUidOptOut, + label: "opt_out", + }, + { + status: metrics.SetUidGDPRHostCookieBlocked, + label: "gdpr_blocked_host_cookie", + }, + { + status: metrics.SetUidSyncerUnknown, + label: "syncer_unknown", + }, + } + + for _, test := range tests { + m := createMetricsForTesting() + + m.RecordSetUid(test.status) + + assertCounterVecValue(t, "", "setuid_requests:"+test.label, m.setUid, + float64(1), + prometheus.Labels{ + statusLabel: string(test.status), + }) + } +} + +func TestRecordSyncerSetMetric(t *testing.T) { + key := "anyKey" + + tests := []struct { + status metrics.SyncerSetUidStatus + label string + }{ + { + status: metrics.SyncerSetUidOK, + label: "ok", + }, + { + status: metrics.SyncerSetUidCleared, + label: "cleared", + }, + } + + for _, test := range tests { + m := createMetricsForTesting() + + m.RecordSyncerSet(key, test.status) + + assertCounterVecValue(t, "", "syncer_sets:"+test.label, m.syncerSets, + float64(1), + prometheus.Labels{ + syncerLabel: key, + statusLabel: string(test.status), + }) + } } func TestPrebidCacheRequestTimeMetric(t *testing.T) { @@ -1349,7 +1434,8 @@ func TestDisabledMetrics(t *testing.T) { }, config.DisabledMetrics{ AdapterConnectionMetrics: true, AdapterGDPRRequestBlocked: true, - }) + }, + nil) // Assert counter vector was not initialized assert.Nil(t, prometheusMetrics.adapterReusedConnections, "Counter Vector adapterReusedConnections should be nil") diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 0e5c80636db..4e2d1ff5ba1 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -7,15 +7,6 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -func actionsAsString() []string { - values := metrics.RequestActions() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - func adaptersAsString() []string { values := openrtb_ext.CoreBidderNames() valuesAsString := make([]string, len(values)) @@ -41,6 +32,15 @@ func boolValuesAsString() []string { } } +func cacheResultsAsString() []string { + values := metrics.CacheResults() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + func cookieTypesAsString() []string { values := metrics.CookieTypes() valuesAsString := make([]string, len(values)) @@ -50,8 +50,8 @@ func cookieTypesAsString() []string { return valuesAsString } -func cacheResultsAsString() []string { - values := metrics.CacheResults() +func cookieSyncStatusesAsString() []string { + values := metrics.CookieSyncStatuses() valuesAsString := make([]string, len(values)) for i, v := range values { valuesAsString[i] = string(v) @@ -68,6 +68,24 @@ func requestStatusesAsString() []string { return valuesAsString } +func syncerRequestStatusesAsString() []string { + values := metrics.SyncerRequestStatuses() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + +func syncerSetStatusesAsString() []string { + values := metrics.SyncerSetUidStatuses() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + func requestTypesAsString() []string { values := metrics.RequestTypes() valuesAsString := make([]string, len(values)) @@ -77,6 +95,15 @@ func requestTypesAsString() []string { return valuesAsString } +func setUidStatusesAsString() []string { + values := metrics.SetUidStatuses() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + func storedDataTypesAsString() []string { values := metrics.StoredDataTypes() valuesAsString := make([]string, len(values)) diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go index 52840d95d9c..c05b9a8c00d 100644 --- a/pbs/pbsrequest.go +++ b/pbs/pbsrequest.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/cache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/stored_requests" @@ -21,6 +20,7 @@ import ( "github.com/blang/semver" "github.com/buger/jsonparser" "github.com/golang/glog" + "github.com/mxmCherry/openrtb/v15/openrtb2" "golang.org/x/net/publicsuffix" ) @@ -121,19 +121,25 @@ type SDK struct { } type PBSBidder struct { - BidderCode string `json:"bidder"` - AdUnitCode string `json:"ad_unit,omitempty"` // for index to dedup responses - ResponseTime int `json:"response_time_ms,omitempty"` - NumBids int `json:"num_bids,omitempty"` - Error string `json:"error,omitempty"` - NoCookie bool `json:"no_cookie,omitempty"` - NoBid bool `json:"no_bid,omitempty"` - UsersyncInfo *usersync.UsersyncInfo `json:"usersync,omitempty"` - Debug []*BidderDebug `json:"debug,omitempty"` + BidderCode string `json:"bidder"` + AdUnitCode string `json:"ad_unit,omitempty"` // for index to dedup responses + ResponseTime int `json:"response_time_ms,omitempty"` + NumBids int `json:"num_bids,omitempty"` + Error string `json:"error,omitempty"` + NoCookie bool `json:"no_cookie,omitempty"` + NoBid bool `json:"no_bid,omitempty"` + UsersyncInfo *UsersyncInfo `json:"usersync,omitempty"` + Debug []*BidderDebug `json:"debug,omitempty"` AdUnits []PBSAdUnit `json:"-"` } +type UsersyncInfo struct { + URL string `json:"url,omitempty"` + Type string `json:"type,omitempty"` + SupportCORS bool `json:"supportCORS,omitempty"` +} + func (bidder *PBSBidder) LookupBidID(Code string) string { for _, unit := range bidder.AdUnits { if unit.Code == Code { @@ -168,12 +174,12 @@ type PBSRequest struct { SDK *SDK `json:"sdk"` // internal - Bidders []*PBSBidder `json:"-"` - User *openrtb2.User `json:"-"` - Cookie *usersync.PBSCookie `json:"-"` - Url string `json:"-"` - Domain string `json:"-"` - Regs *openrtb2.Regs `json:"-"` + Bidders []*PBSBidder `json:"-"` + User *openrtb2.User `json:"-"` + Cookie *usersync.Cookie `json:"-"` + Url string `json:"-"` + Domain string `json:"-"` + Regs *openrtb2.Regs `json:"-"` Start time.Time } @@ -217,7 +223,7 @@ func ParseMediaTypes(types []string) []MediaType { return mtypes } -var ipv4Validator iputil.IPValidator = iputil.VersionIPValidator{iputil.IPv4} +var ipv4Validator iputil.IPValidator = iputil.VersionIPValidator{Version: iputil.IPv4} func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.Cache, hostCookieConfig *config.HostCookie) (*PBSRequest, error) { defer r.Body.Close() @@ -264,7 +270,7 @@ func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.C // use client-side data for web requests if pbsReq.App == nil { - pbsReq.Cookie = usersync.ParsePBSCookieFromRequest(r, hostCookieConfig) + pbsReq.Cookie = usersync.ParseCookieFromRequest(r, hostCookieConfig) pbsReq.Device.UA = r.Header.Get("User-Agent") diff --git a/pbs/usersync.go b/pbs/usersync.go index 4cac3544804..85b55f42aeb 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -92,8 +92,8 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr return } - pc := usersync.ParsePBSCookieFromRequest(r, deps.HostCookieConfig) - pc.SetPreference(optout == "") + pc := usersync.ParseCookieFromRequest(r, deps.HostCookieConfig) + pc.SetOptOut(optout != "") pc.SetCookieOnResponse(w, false, deps.HostCookieConfig, deps.HostCookieConfig.TTLDuration()) diff --git a/router/router.go b/router/router.go index e1c42699225..446d4280b09 100644 --- a/router/router.go +++ b/router/router.go @@ -12,12 +12,6 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints/events" - "github.com/prebid/prebid-server/errortypes" - - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/appnexus" @@ -33,11 +27,15 @@ import ( "github.com/prebid/prebid-server/cache/filecache" "github.com/prebid/prebid-server/cache/postgrescache" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/endpoints" + "github.com/prebid/prebid-server/endpoints/events" infoEndpoints "github.com/prebid/prebid-server/endpoints/info" "github.com/prebid/prebid-server/endpoints/openrtb2" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" @@ -45,7 +43,8 @@ import ( "github.com/prebid/prebid-server/router/aspects" "github.com/prebid/prebid-server/server/ssl" storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" - "github.com/prebid/prebid-server/usersync/usersyncers" + "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/sliceutil" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -212,8 +211,36 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R legacyBidderList := openrtb_ext.CoreBidderNames() legacyBidderList = append(legacyBidderList, openrtb_ext.BidderName("districtm")) + p, _ := filepath.Abs(infoDirectory) + bidderInfos, err := config.LoadBidderInfoFromDisk(p, cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + return nil, err + } + + if err := applyBidderInfoConfigOverrides(bidderInfos, cfg.Adapters); err != nil { + return nil, err + } + + if err := checkSupportedUserSyncEndpoints(bidderInfos); err != nil { + return nil, err + } + + syncersByBidder, errs := usersync.BuildSyncers(cfg, bidderInfos) + if len(errs) > 0 { + return nil, errortypes.NewAggregateError("user sync", errs) + } + + syncerKeys := make([]string, 0, len(syncersByBidder)) + syncerKeysHashSet := map[string]struct{}{} + for _, syncer := range syncersByBidder { + syncerKeysHashSet[syncer.Key()] = struct{}{} + } + for k := range syncerKeysHashSet { + syncerKeys = append(syncerKeys, k) + } + // Metrics engine - r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList) + r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList, syncerKeys) db, shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) // todo(zachbadgett): better shutdown r.Shutdown = shutdown @@ -228,21 +255,14 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R glog.Fatalf("Failed to create the bidder params validator. %v", err) } - p, _ := filepath.Abs(infoDirectory) - bidderInfos, err := config.LoadBidderInfoFromDisk(p, cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) - if err != nil { - glog.Fatal(err) - } - activeBidders := exchange.GetActiveBidders(bidderInfos) disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos) defaultAliases, defReqJSON := readDefaultRequest(cfg.DefReqConfig) if err := validateDefaultAliases(defaultAliases); err != nil { - glog.Fatal(err) + return nil, err } - syncers := usersyncers.NewSyncerMap(cfg) gvlVendorIDs := bidderInfos.ToGVLVendorIDMap() gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, gvlVendorIDs, generalHttpClient) @@ -252,10 +272,10 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, bidderInfos, r.MetricsEngine) if len(adaptersErrs) > 0 { errs := errortypes.NewAggregateError("Failed to initialize adapters", adaptersErrs) - glog.Fatalf("%v", errs) + return nil, errs } - theExchange := exchange.NewExchange(adapters, cacheClient, cfg, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor, categoriesFetcher) + theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor, categoriesFetcher) openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) if err != nil { @@ -277,14 +297,14 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, metrics.ReqTypeVideo) } - r.POST("/auction", endpoints.Auction(cfg, syncers, gdprPerms, r.MetricsEngine, dataCache, exchanges)) + r.POST("/auction", endpoints.Auction(cfg, syncersByBidder, gdprPerms, r.MetricsEngine, dataCache, exchanges)) r.POST("/openrtb2/auction", openrtbEndpoint) r.POST("/openrtb2/video", videoEndpoint) r.GET("/openrtb2/amp", ampEndpoint) r.GET("/info/bidders", infoEndpoints.NewBiddersEndpoint(bidderInfos, defaultAliases)) r.GET("/info/bidders/:bidderName", infoEndpoints.NewBiddersDetailEndpoint(bidderInfos, cfg.Adapters, defaultAliases)) r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator, defaultAliases)) - r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncers, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, activeBidders)) + r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, activeBidders).Handle) r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse)) r.GET("/", serveIndex) r.ServeFiles("/static/*filepath", http.Dir("static")) @@ -307,7 +327,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R PBSAnalytics: pbsAnalytics, } - r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg.HostCookie, syncers, gdprPerms, pbsAnalytics, r.MetricsEngine)) + r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg.HostCookie, syncersByBidder, gdprPerms, pbsAnalytics, r.MetricsEngine)) r.GET("/getuids", endpoints.NewGetUIDsEndpoint(cfg.HostCookie)) r.POST("/optout", userSyncDeps.OptOut) r.GET("/optout", userSyncDeps.OptOut) @@ -315,6 +335,87 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R return r, nil } +func applyBidderInfoConfigOverrides(bidderInfos config.BidderInfos, adaptersCfg map[string]config.Adapter) error { + for bidderName, bidderInfo := range bidderInfos { + if adapterCfg, exists := adaptersCfg[bidderName]; exists { + bidderInfo.Syncer = adapterCfg.Syncer.Override(bidderInfo.Syncer) + + // validate and try to apply the legacy usersync_url configuration in attempt to provide + // an easier upgrade path. be warned, this will break if the bidder adds a second syncer + // type and will eventually be removed after we've given hosts enough time to upgrade to + // the new config. + if adapterCfg.UserSyncURL != "" { + if bidderInfo.Syncer == nil { + return fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder does not define a user sync", strings.ToLower(bidderName)) + } + + endpointsCount := 0 + if bidderInfo.Syncer.IFrame != nil { + bidderInfo.Syncer.IFrame.URL = adapterCfg.UserSyncURL + endpointsCount++ + } + if bidderInfo.Syncer.Redirect != nil { + bidderInfo.Syncer.Redirect.URL = adapterCfg.UserSyncURL + endpointsCount++ + } + + // use Supports as a hint if there are no good defaults provided + if endpointsCount == 0 { + if sliceutil.ContainsStringIgnoreCase(bidderInfo.Syncer.Supports, "iframe") { + bidderInfo.Syncer.IFrame = &config.SyncerEndpoint{URL: adapterCfg.UserSyncURL} + endpointsCount++ + } + if sliceutil.ContainsStringIgnoreCase(bidderInfo.Syncer.Supports, "redirect") { + bidderInfo.Syncer.Redirect = &config.SyncerEndpoint{URL: adapterCfg.UserSyncURL} + endpointsCount++ + } + } + + if endpointsCount == 0 { + return fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder does not define user sync endpoints and does not define supported endpoints", strings.ToLower(bidderName)) + } + + // if the bidder defines both an iframe and redirect endpoint, we can't be sure which config value to + // override, and it wouldn't be both. this is a fatal configuration error. + if endpointsCount > 1 { + return fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", strings.ToLower(bidderName)) + } + + // provide a warning that this compatibility layer is temporary + glog.Warningf("adapters.%s.usersync_url is deprecated and will be removed in a future version, please update to the latest user sync config values", strings.ToLower(bidderName)) + } + + bidderInfos[bidderName] = bidderInfo + } + } + return nil +} + +func checkSupportedUserSyncEndpoints(bidderInfos config.BidderInfos) error { + for name, info := range bidderInfos { + if info.Syncer == nil { + continue + } + + for _, endpoint := range info.Syncer.Supports { + endpointLower := strings.ToLower(endpoint) + switch endpointLower { + case "iframe": + if info.Syncer.IFrame == nil { + glog.Warningf("bidder %s supports iframe user sync, but doesn't have a default and must be configured by the host", name) + } + case "redirect": + if info.Syncer.Redirect == nil { + glog.Warningf("bidder %s supports redirect user sync, but doesn't have a default and must be configured by the host", name) + } + default: + return fmt.Errorf("failed to load bidder info for %s, user sync supported endpoint '%s' is unrecognized", name, endpoint) + } + } + } + return nil +} + // Fixes #648 // // These CORS options pose a security risk... but it's a calculated one. diff --git a/router/router_test.go b/router/router_test.go index 65fe299e309..07e5938800e 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -74,6 +74,178 @@ func TestExchangeMap(t *testing.T) { } } +func TestApplyBidderInfoConfigOverrides(t *testing.T) { + var testCases = []struct { + description string + givenBidderInfos config.BidderInfos + givenAdaptersCfg map[string]config.Adapter + expectedError string + expectedBidderInfos config.BidderInfos + }{ + { + description: "Syncer Override", + givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Key: "original"}}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {Syncer: &config.Syncer{Key: "override"}}}, + expectedBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Key: "override"}}}, + }, + { + description: "UserSyncURL Override IFrame", + givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{IFrame: &config.SyncerEndpoint{URL: "original"}}}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, + expectedBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{IFrame: &config.SyncerEndpoint{URL: "override"}}}}, + }, + { + description: "UserSyncURL Supports IFrame", + givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Supports: []string{"iframe"}}}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, + expectedBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Supports: []string{"iframe"}, IFrame: &config.SyncerEndpoint{URL: "override"}}}}, + }, + { + description: "UserSyncURL Override Redirect", + givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Supports: []string{"redirect"}}}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, + expectedBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Supports: []string{"redirect"}, Redirect: &config.SyncerEndpoint{URL: "override"}}}}, + }, + { + description: "UserSyncURL Supports Redirect", + givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Redirect: &config.SyncerEndpoint{URL: "original"}}}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, + expectedBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Redirect: &config.SyncerEndpoint{URL: "override"}}}}, + }, + { + description: "UserSyncURL Override Syncer Not Defined", + givenBidderInfos: config.BidderInfos{"a": {}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, + expectedError: "adapters.a.usersync_url cannot be applied, bidder does not define a user sync", + }, + { + description: "UserSyncURL Override Syncer Endpoints Not Defined", + givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{}}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, + expectedError: "adapters.a.usersync_url cannot be applied, bidder does not define user sync endpoints and does not define supported endpoints", + }, + { + description: "UserSyncURL Override Ambiguous", + givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{IFrame: &config.SyncerEndpoint{URL: "originalIFrame"}, Redirect: &config.SyncerEndpoint{URL: "originalRedirect"}}}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, + expectedError: "adapters.a.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", + }, + { + description: "UserSyncURL Supports Ambiguous", + givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Supports: []string{"iframe", "redirect"}}}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, + expectedError: "adapters.a.usersync_url cannot be applied, bidder defines multiple user sync endpoints or supports multiple endpoints", + }, + } + + for _, test := range testCases { + resultErr := applyBidderInfoConfigOverrides(test.givenBidderInfos, test.givenAdaptersCfg) + if test.expectedError == "" { + assert.NoError(t, resultErr, test.description+":err") + assert.Equal(t, test.expectedBidderInfos, test.givenBidderInfos, test.description+":result") + } else { + assert.EqualError(t, resultErr, test.expectedError, test.description+":err") + } + } +} + +func TestCheckSupportedUserSyncEndpoints(t *testing.T) { + anyEndpoint := &config.SyncerEndpoint{URL: "anyURL"} + + var testCases = []struct { + description string + givenBidderInfos config.BidderInfos + expectedError string + }{ + { + description: "None", + givenBidderInfos: config.BidderInfos{}, + expectedError: "", + }, + { + description: "One - No Syncer", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{Syncer: nil}, + }, + expectedError: "", + }, + { + description: "One - Invalid Supported Endpoint", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"invalid"}}}, + }, + expectedError: "failed to load bidder info for a, user sync supported endpoint 'invalid' is unrecognized", + }, + { + description: "One - IFrame Supported - Not Specified", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"iframe"}, IFrame: nil}}, + }, + expectedError: "", + }, + { + description: "One - IFrame Supported - Specified", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"iframe"}, IFrame: anyEndpoint}}, + }, + expectedError: "", + }, + { + description: "One - Redirect Supported - Not Specified", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"redirect"}, Redirect: nil}}, + }, + expectedError: "", + }, + { + description: "One - IFrame Supported - Specified", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"redirect"}, Redirect: anyEndpoint}}, + }, + expectedError: "", + }, + { + description: "One - IFrame + Redirect Supported - Not Specified", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"iframe", "redirect"}, IFrame: nil, Redirect: nil}}, + }, + expectedError: "", + }, + { + description: "One - IFrame + Redirect Supported - Specified", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"iframe", "redirect"}, IFrame: anyEndpoint, Redirect: anyEndpoint}}, + }, + expectedError: "", + }, + { + description: "Many - With Invalid Supported Endpoint", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{}, + "b": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"invalid"}}}, + }, + expectedError: "failed to load bidder info for b, user sync supported endpoint 'invalid' is unrecognized", + }, + { + description: "Many - Specified + Not Specified", + givenBidderInfos: config.BidderInfos{ + "a": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"iframe"}, IFrame: anyEndpoint}}, + "b": config.BidderInfo{Syncer: &config.Syncer{Supports: []string{"redirect"}, Redirect: nil}}, + }, + expectedError: "", + }, + } + + for _, test := range testCases { + resultErr := checkSupportedUserSyncEndpoints(test.givenBidderInfos) + if test.expectedError == "" { + assert.NoError(t, resultErr, test.description) + } else { + assert.EqualError(t, resultErr, test.expectedError, test.description) + } + } +} + // Prevents #648 func TestCORSSupport(t *testing.T) { const origin = "https://publisher-domain.com" diff --git a/server/listener_test.go b/server/listener_test.go index f91dbddbc54..750d0ed8dda 100644 --- a/server/listener_test.go +++ b/server/listener_test.go @@ -25,7 +25,7 @@ func TestCloseErrorMetrics(t *testing.T) { func doTest(t *testing.T, allowAccept bool, allowClose bool) { reg := gometrics.NewRegistry() - me := metrics.NewMetrics(reg, nil, config.DisabledMetrics{}) + me := metrics.NewMetrics(reg, nil, config.DisabledMetrics{}, nil) var listener net.Listener = &mockListener{ listenSuccess: allowAccept, diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index 044cfe140b2..1ee5366f969 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -6,3 +6,7 @@ capabilities: mediaTypes: - banner - video +userSync: + iframe: + url: "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru={{.RedirectURL}}&id=zzz000000000002zzz" + userMacro: "33XUSERID33X" \ No newline at end of file diff --git a/static/bidder-info/acuityads.yaml b/static/bidder-info/acuityads.yaml index 9539e36b91e..c806294d644 100644 --- a/static/bidder-info/acuityads.yaml +++ b/static/bidder-info/acuityads.yaml @@ -12,4 +12,7 @@ capabilities: - banner - video - native - +userSync: + redirect: + url: "https://cs.admanmedia.com/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[UID]" \ No newline at end of file diff --git a/static/bidder-info/adagio.yaml b/static/bidder-info/adagio.yaml index 3661191b3a1..891b2a6caea 100644 --- a/static/bidder-info/adagio.yaml +++ b/static/bidder-info/adagio.yaml @@ -9,4 +9,8 @@ capabilities: mediaTypes: - banner - video - - native \ No newline at end of file + - native +userSync: + redirect: + url: "https://mp.4dex.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "{{UID}}" diff --git a/static/bidder-info/adf.yaml b/static/bidder-info/adf.yaml index 776e208f562..2e312bff788 100644 --- a/static/bidder-info/adf.yaml +++ b/static/bidder-info/adf.yaml @@ -12,3 +12,7 @@ capabilities: - banner - native - video +userSync: + redirect: + url: "https://cm.adform.net/cookie?redirect_url={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index 461714ac44d..c8c1d91565a 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://cm.adform.net/cookie?redirect_url={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/adkernel.yaml b/static/bidder-info/adkernel.yaml index a78b3cde498..8d9096b271c 100644 --- a/static/bidder-info/adkernel.yaml +++ b/static/bidder-info/adkernel.yaml @@ -9,3 +9,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "{UID}" \ No newline at end of file diff --git a/static/bidder-info/adkernelAdn.yaml b/static/bidder-info/adkernelAdn.yaml index 1f54f8e5a8f..e401cb83923 100644 --- a/static/bidder-info/adkernelAdn.yaml +++ b/static/bidder-info/adkernelAdn.yaml @@ -9,3 +9,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "{UID}" diff --git a/static/bidder-info/adman.yaml b/static/bidder-info/adman.yaml index 2db7c07584c..d7869fdc536 100644 --- a/static/bidder-info/adman.yaml +++ b/static/bidder-info/adman.yaml @@ -9,4 +9,8 @@ capabilities: site: mediaTypes: - banner - - video \ No newline at end of file + - video +userSync: + redirect: + url: "https://sync.admanmedia.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/admixer.yaml b/static/bidder-info/admixer.yaml index 9faf0eb3a3e..5cb07ef5579 100644 --- a/static/bidder-info/admixer.yaml +++ b/static/bidder-info/admixer.yaml @@ -13,4 +13,8 @@ capabilities: - banner - video - native - - audio \ No newline at end of file + - audio +userSync: + redirect: + url: "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl={{.RedirectURL}}" + userMacro: "$$visitor_cookie$$" diff --git a/static/bidder-info/adocean.yaml b/static/bidder-info/adocean.yaml index 6374d752c34..680e7496725 100644 --- a/static/bidder-info/adocean.yaml +++ b/static/bidder-info/adocean.yaml @@ -8,3 +8,8 @@ capabilities: site: mediaTypes: - banner +userSync: + # adocean supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/adpone.yaml b/static/bidder-info/adpone.yaml index 7c5b473770b..c5fb83fb39c 100644 --- a/static/bidder-info/adpone.yaml +++ b/static/bidder-info/adpone.yaml @@ -8,3 +8,7 @@ capabilities: site: mediaTypes: - banner +userSync: + redirect: + url: "https://usersync.adpone.com/csync?redir={{.RedirectURL}}" + userMacro: "{uid}" \ No newline at end of file diff --git a/static/bidder-info/adtarget.yaml b/static/bidder-info/adtarget.yaml index cc064b9ca6b..9ef7e259d22 100644 --- a/static/bidder-info/adtarget.yaml +++ b/static/bidder-info/adtarget.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + # adtarget supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe \ No newline at end of file diff --git a/static/bidder-info/adtelligent.yaml b/static/bidder-info/adtelligent.yaml index 30f55ea912e..f8ce434d481 100644 --- a/static/bidder-info/adtelligent.yaml +++ b/static/bidder-info/adtelligent.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + # adtelligent supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe \ No newline at end of file diff --git a/static/bidder-info/advangelists.yaml b/static/bidder-info/advangelists.yaml index aed9900d0e7..35495d4d97f 100644 --- a/static/bidder-info/advangelists.yaml +++ b/static/bidder-info/advangelists.yaml @@ -10,4 +10,8 @@ capabilities: mediaTypes: - banner - video - +userSync: + iframe: + url: "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect={{.RedirectURL}}" + userMacro: "$UID" + diff --git a/static/bidder-info/adxcg.yaml b/static/bidder-info/adxcg.yaml index e5535ae7258..14c7c8bb572 100644 --- a/static/bidder-info/adxcg.yaml +++ b/static/bidder-info/adxcg.yaml @@ -11,3 +11,8 @@ capabilities: - banner - video - native +userSync: + # adxcg supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml index d9b23d6c1a1..a8769ddd854 100644 --- a/static/bidder-info/adyoulike.yaml +++ b/static/bidder-info/adyoulike.yaml @@ -8,3 +8,7 @@ capabilities: - banner - video - native +userSync: + redirect: + url: "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" + userMacro: "[BUYER_USERID]" diff --git a/static/bidder-info/aja.yaml b/static/bidder-info/aja.yaml index 53f43689172..1d18dd7e75d 100644 --- a/static/bidder-info/aja.yaml +++ b/static/bidder-info/aja.yaml @@ -10,4 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "%s" diff --git a/static/bidder-info/amx.yaml b/static/bidder-info/amx.yaml index f9fdfbb4a41..43c33c43ce0 100644 --- a/static/bidder-info/amx.yaml +++ b/static/bidder-info/amx.yaml @@ -9,4 +9,8 @@ capabilities: app: mediaTypes: - banner - - video \ No newline at end of file + - video +userSync: + redirect: + url: "https://prebid.a-mo.net/cchain/0?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "" diff --git a/static/bidder-info/appnexus.yaml b/static/bidder-info/appnexus.yaml index f2f4a1266df..41838159bbe 100644 --- a/static/bidder-info/appnexus.yaml +++ b/static/bidder-info/appnexus.yaml @@ -12,3 +12,8 @@ capabilities: - banner - video - native +userSync: + key: "adnxs" + redirect: + url: "https://ib.adnxs.com/getuid?{{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/audienceNetwork.yaml b/static/bidder-info/audienceNetwork.yaml index 324e5c6dff8..5e567318a89 100644 --- a/static/bidder-info/audienceNetwork.yaml +++ b/static/bidder-info/audienceNetwork.yaml @@ -6,3 +6,7 @@ capabilities: - banner - video - native +userSync: + # facebook's audience network supports user syncing, but requires configuration by the host. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/avocet.yaml b/static/bidder-info/avocet.yaml index 42147c96ebf..ad8ca157a75 100644 --- a/static/bidder-info/avocet.yaml +++ b/static/bidder-info/avocet.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" + userMacro: "{{UUID}}" diff --git a/static/bidder-info/beachfront.yaml b/static/bidder-info/beachfront.yaml index 06991698090..64e04dc6eec 100644 --- a/static/bidder-info/beachfront.yaml +++ b/static/bidder-info/beachfront.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + iframe: + url: "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" + userMacro: "[io_cid]" diff --git a/static/bidder-info/beintoo.yaml b/static/bidder-info/beintoo.yaml index 905d89a44c4..d804a29818d 100644 --- a/static/bidder-info/beintoo.yaml +++ b/static/bidder-info/beintoo.yaml @@ -5,3 +5,8 @@ capabilities: site: mediaTypes: - banner +userSync: + key: "Beintoo" + iframe: + url: "https://ib.beintoo.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/between.yaml b/static/bidder-info/between.yaml index 71bd8ba6256..5649c328d62 100644 --- a/static/bidder-info/between.yaml +++ b/static/bidder-info/between.yaml @@ -7,4 +7,8 @@ capabilities: - banner app: mediaTypes: - - banner \ No newline at end of file + - banner +userSync: + redirect: + url: "https://ads.betweendigital.com/match?bidder_id=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback_url={{.RedirectURL}}" + userMacro: "${USER_ID}" diff --git a/static/bidder-info/bidmyadz.yaml b/static/bidder-info/bidmyadz.yaml index 70a995a2798..41cf4ecaa0a 100644 --- a/static/bidder-info/bidmyadz.yaml +++ b/static/bidder-info/bidmyadz.yaml @@ -10,4 +10,8 @@ capabilities: mediaTypes: - banner - video - - native \ No newline at end of file + - native +userSync: + redirect: + url: "https://cookie-sync.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&red={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/bmtm.yaml b/static/bidder-info/bmtm.yaml index c13bf17db73..bc7d2aed806 100644 --- a/static/bidder-info/bmtm.yaml +++ b/static/bidder-info/bmtm.yaml @@ -6,3 +6,8 @@ capabilities: mediaTypes: - banner - video +userSync: + # bmtm supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/brightroll.yaml b/static/bidder-info/brightroll.yaml index a2ea0c74b77..db822bc4f7b 100644 --- a/static/bidder-info/brightroll.yaml +++ b/static/bidder-info/brightroll.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/colossus.yaml b/static/bidder-info/colossus.yaml index 901c824c603..11189c62cf3 100644 --- a/static/bidder-info/colossus.yaml +++ b/static/bidder-info/colossus.yaml @@ -9,3 +9,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://sync.colossusssp.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[UID]" \ No newline at end of file diff --git a/static/bidder-info/connectad.yaml b/static/bidder-info/connectad.yaml index fa0ae4520fc..63cf05427de 100644 --- a/static/bidder-info/connectad.yaml +++ b/static/bidder-info/connectad.yaml @@ -8,3 +8,8 @@ capabilities: site: mediaTypes: - banner +userSync: + iframe: + url: "https://cdn.connectad.io/connectmyusers.php?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "" + # connectad appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/consumable.yaml b/static/bidder-info/consumable.yaml index e9b5f72623c..5200c738200 100644 --- a/static/bidder-info/consumable.yaml +++ b/static/bidder-info/consumable.yaml @@ -8,3 +8,8 @@ capabilities: site: mediaTypes: - banner +userSync: + redirect: + url: "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "" + # consumable appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/conversant.yaml b/static/bidder-info/conversant.yaml index aa3d3822802..1d298f8b582 100644 --- a/static/bidder-info/conversant.yaml +++ b/static/bidder-info/conversant.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl={{.RedirectURL}}" + userMacro: "" + # conversant appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/cpmstar.yaml b/static/bidder-info/cpmstar.yaml index 097dfddd5b0..46a7451bd1c 100644 --- a/static/bidder-info/cpmstar.yaml +++ b/static/bidder-info/cpmstar.yaml @@ -9,3 +9,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml index bfa098ba39d..5eb7eb37512 100644 --- a/static/bidder-info/criteo.yaml +++ b/static/bidder-info/criteo.yaml @@ -7,4 +7,9 @@ capabilities: - banner site: mediaTypes: - - banner \ No newline at end of file + - banner +userSync: + # criteo supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/datablocks.yaml b/static/bidder-info/datablocks.yaml index 43f00a63eae..415dbc3546e 100644 --- a/static/bidder-info/datablocks.yaml +++ b/static/bidder-info/datablocks.yaml @@ -11,3 +11,7 @@ capabilities: - banner - native - video +userSync: + redirect: + url: "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "${uid}" diff --git a/static/bidder-info/deepintent.yaml b/static/bidder-info/deepintent.yaml index a8f17a55d6f..10ef458f133 100644 --- a/static/bidder-info/deepintent.yaml +++ b/static/bidder-info/deepintent.yaml @@ -8,3 +8,7 @@ capabilities: site: mediaTypes: - banner +userSync: + redirect: + url: "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/dmx.yaml b/static/bidder-info/dmx.yaml index d29d699daeb..fa31886d229 100644 --- a/static/bidder-info/dmx.yaml +++ b/static/bidder-info/dmx.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + # dmx supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/e_volution.yaml b/static/bidder-info/e_volution.yaml index 6ea9dc7bac2..ddfef3dff36 100644 --- a/static/bidder-info/e_volution.yaml +++ b/static/bidder-info/e_volution.yaml @@ -11,4 +11,8 @@ capabilities: mediaTypes: - banner - video - - native \ No newline at end of file + - native +userSync: + redirect: + url: "https://sync.e-volution.ai/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/emx_digital.yaml b/static/bidder-info/emx_digital.yaml index ec0d090fb4c..a4cbd095934 100644 --- a/static/bidder-info/emx_digital.yaml +++ b/static/bidder-info/emx_digital.yaml @@ -9,4 +9,8 @@ capabilities: app: mediaTypes: - banner - - video \ No newline at end of file + - video +userSync: + iframe: + url: "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/engagebdr.yaml b/static/bidder-info/engagebdr.yaml index fd08367acc7..f79bca69953 100644 --- a/static/bidder-info/engagebdr.yaml +++ b/static/bidder-info/engagebdr.yaml @@ -7,3 +7,8 @@ capabilities: - banner - video - native +userSync: + iframe: + url: "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "${UUID}" + diff --git a/static/bidder-info/eplanning.yaml b/static/bidder-info/eplanning.yaml index ab0b7609dbb..960244446aa 100644 --- a/static/bidder-info/eplanning.yaml +++ b/static/bidder-info/eplanning.yaml @@ -8,3 +8,7 @@ capabilities: site: mediaTypes: - banner +userSync: + iframe: + url: "https://ads.us.e-planning.net/uspd/1/?du={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/gamma.yaml b/static/bidder-info/gamma.yaml index aedaf2cf749..4a79fafb0fd 100644 --- a/static/bidder-info/gamma.yaml +++ b/static/bidder-info/gamma.yaml @@ -5,3 +5,8 @@ capabilities: mediaTypes: - banner - video +userSync: + # gamma supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe \ No newline at end of file diff --git a/static/bidder-info/gamoshi.yaml b/static/bidder-info/gamoshi.yaml index 0cfd495762f..c72d1770082 100644 --- a/static/bidder-info/gamoshi.yaml +++ b/static/bidder-info/gamoshi.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://rtb.gamoshi.io/user_sync_prebid?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl={{.RedirectURL}}" + userMacro: "[gusr]" diff --git a/static/bidder-info/grid.yaml b/static/bidder-info/grid.yaml index 31c7b7320e3..b6c13bc7000 100644 --- a/static/bidder-info/grid.yaml +++ b/static/bidder-info/grid.yaml @@ -9,4 +9,8 @@ capabilities: site: mediaTypes: - banner - - video \ No newline at end of file + - video +userSync: + redirect: + url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" + userMacro: "${BSW_UUID}" diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml index bfefe63ab40..34b6f47d39e 100644 --- a/static/bidder-info/gumgum.yaml +++ b/static/bidder-info/gumgum.yaml @@ -5,4 +5,9 @@ capabilities: site: mediaTypes: - banner - - video \ No newline at end of file + - video +userSync: + iframe: + url: "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "" + # gumgum appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/improvedigital.yaml b/static/bidder-info/improvedigital.yaml index f7fea4a8402..649246abf09 100644 --- a/static/bidder-info/improvedigital.yaml +++ b/static/bidder-info/improvedigital.yaml @@ -12,3 +12,7 @@ capabilities: - banner - video - native +userSync: + redirect: + url: "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "{PUB_USER_ID}" diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index d62a2c9239d..43040658964 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -11,3 +11,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://sync.inmobi.com/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "{ID5UID}" diff --git a/static/bidder-info/invibes.yaml b/static/bidder-info/invibes.yaml index 45184fb9f65..6b7cd6a1f3c 100644 --- a/static/bidder-info/invibes.yaml +++ b/static/bidder-info/invibes.yaml @@ -5,3 +5,8 @@ capabilities: site: mediaTypes: - banner +userSync: + # invibes supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe \ No newline at end of file diff --git a/static/bidder-info/ix.yaml b/static/bidder-info/ix.yaml index 1e89c72e5bb..8c46525caf0 100644 --- a/static/bidder-info/ix.yaml +++ b/static/bidder-info/ix.yaml @@ -14,3 +14,8 @@ capabilities: - video - native - audio +userSync: + redirect: + url: "https://ssum.casalemedia.com/usermatchredir?s=194962&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "" + # ix appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/jixie.yaml b/static/bidder-info/jixie.yaml index ac38f313da1..0553f1f7393 100644 --- a/static/bidder-info/jixie.yaml +++ b/static/bidder-info/jixie.yaml @@ -6,3 +6,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "%%JXUID%%" diff --git a/static/bidder-info/krushmedia.yaml b/static/bidder-info/krushmedia.yaml index 342e11df2c7..82055a29988 100644 --- a/static/bidder-info/krushmedia.yaml +++ b/static/bidder-info/krushmedia.yaml @@ -10,4 +10,8 @@ capabilities: mediaTypes: - banner - video - - native \ No newline at end of file + - native +userSync: + redirect: + url: "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/lockerdome.yaml b/static/bidder-info/lockerdome.yaml index b005e7c1f85..cdb0434935d 100644 --- a/static/bidder-info/lockerdome.yaml +++ b/static/bidder-info/lockerdome.yaml @@ -7,3 +7,22 @@ capabilities: site: mediaTypes: - banner +userSync: + # lockerdome supports user syncing, but requires configuration by the host. replace <> in + # the url below and configure it as either an environment variable or in pbs.yaml. + # + # environment variable: + # ---------------------- + # PBS_ADAPTERS_LOCKERDOME_USERSYNC_REDIRECT_URL: "https://lockerdome.com/usync/prebidserver?pid=<>&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + # PBS_ADAPTERS_LOCKERDOME_USERSYNC_REDIRECT_USER_MACRO: "{{uid}}" + # + # pbs.yaml: + # --------- + # adapters: + # lockerdome: + # usersync: + # redirect: + # url: "https://lockerdome.com/usync/prebidserver?pid=<>&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + # userMacro: "{{uid}}" + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/logicad.yaml b/static/bidder-info/logicad.yaml index c087516c061..4748cc75f09 100644 --- a/static/bidder-info/logicad.yaml +++ b/static/bidder-info/logicad.yaml @@ -7,4 +7,8 @@ capabilities: app: mediaTypes: - banner +userSync: + redirect: + url: "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/lunamedia.yaml b/static/bidder-info/lunamedia.yaml index 4cabdc4a381..216fae9becf 100644 --- a/static/bidder-info/lunamedia.yaml +++ b/static/bidder-info/lunamedia.yaml @@ -10,4 +10,8 @@ capabilities: mediaTypes: - banner - video - +userSync: + iframe: + url: "https://api.lunamedia.io/xp/user-sync?redirect={{.RedirectURL}}" + userMacro: "$UID" + diff --git a/static/bidder-info/marsmedia.yaml b/static/bidder-info/marsmedia.yaml index 143b893ed9b..3080962d37c 100644 --- a/static/bidder-info/marsmedia.yaml +++ b/static/bidder-info/marsmedia.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "${UUID}" diff --git a/static/bidder-info/mediafuse.yaml b/static/bidder-info/mediafuse.yaml index 98611402905..3c69bfeacaf 100644 --- a/static/bidder-info/mediafuse.yaml +++ b/static/bidder-info/mediafuse.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + # mediafuse supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe \ No newline at end of file diff --git a/static/bidder-info/mgid.yaml b/static/bidder-info/mgid.yaml index bddb8b8598e..a9e20d631c0 100644 --- a/static/bidder-info/mgid.yaml +++ b/static/bidder-info/mgid.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - native +userSync: + redirect: + url: "https://cm.mgid.com/m?cdsp=363893&adu={{.RedirectURL}}" + userMacro: "{muidn}" diff --git a/static/bidder-info/nanointeractive.yaml b/static/bidder-info/nanointeractive.yaml index d199e9e8ff5..3b4db38dff8 100644 --- a/static/bidder-info/nanointeractive.yaml +++ b/static/bidder-info/nanointeractive.yaml @@ -8,3 +8,7 @@ capabilities: site: mediaTypes: - banner +userSync: + redirect: + url: "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/ninthdecimal.yaml b/static/bidder-info/ninthdecimal.yaml index eda7d222a5f..db1392d78c8 100755 --- a/static/bidder-info/ninthdecimal.yaml +++ b/static/bidder-info/ninthdecimal.yaml @@ -10,4 +10,8 @@ capabilities: mediaTypes: - banner - video - +userSync: + iframe: + url: "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect={{.RedirectURL}}" + userMacro: "$UID" + diff --git a/static/bidder-info/nobid.yaml b/static/bidder-info/nobid.yaml index 89f2a28abcd..6fa65fe93bc 100644 --- a/static/bidder-info/nobid.yaml +++ b/static/bidder-info/nobid.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://ads.servenobid.com/getsync?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/onetag.yaml b/static/bidder-info/onetag.yaml index 697ef04d5a1..b0aeeafe347 100644 --- a/static/bidder-info/onetag.yaml +++ b/static/bidder-info/onetag.yaml @@ -11,4 +11,8 @@ capabilities: mediaTypes: - banner - video - - native \ No newline at end of file + - native +userSync: + iframe: + url: "https://onetag-sys.com/usync/?redir={{.RedirectURL}}" + userMacro: "${USER_TOKEN}" diff --git a/static/bidder-info/openx.yaml b/static/bidder-info/openx.yaml index 709f3db0147..c48d7de2933 100644 --- a/static/bidder-info/openx.yaml +++ b/static/bidder-info/openx.yaml @@ -11,3 +11,7 @@ capabilities: - banner - video modifyingVastXmlAllowed: true +userSync: + redirect: + url: "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r={{.RedirectURL}}" + userMacro: "${UID}" diff --git a/static/bidder-info/operaads.yaml b/static/bidder-info/operaads.yaml index b95d81155c1..d28492b139a 100644 --- a/static/bidder-info/operaads.yaml +++ b/static/bidder-info/operaads.yaml @@ -11,4 +11,8 @@ capabilities: mediaTypes: - banner - video - - native \ No newline at end of file + - native +userSync: + redirect: + url: "https://t.adx.opera.com/pbs/sync?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&gdpr_consent={{.GDPRConsent}}&r={{.RedirectURL}}" + userMacro: "${UID}" diff --git a/static/bidder-info/outbrain.yaml b/static/bidder-info/outbrain.yaml index e38ec915f49..d24c603a6dc 100644 --- a/static/bidder-info/outbrain.yaml +++ b/static/bidder-info/outbrain.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - native +userSync: + redirect: + url: "https://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "__ZUID__" diff --git a/static/bidder-info/pubmatic.yaml b/static/bidder-info/pubmatic.yaml index 45c0418af8a..feba7faa110 100644 --- a/static/bidder-info/pubmatic.yaml +++ b/static/bidder-info/pubmatic.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + iframe: + url: "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect={{.RedirectURL}}" + userMacro: "" + # pubmatic appends the user id to end of the redirect url and does not utilize a macro diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index bda03efd99c..635ceb46006 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -14,3 +14,7 @@ capabilities: - video - audio - native +userSync: + redirect: + url: "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl={{.RedirectURL}}" + userMacro: "%%VGUID%%" diff --git a/static/bidder-info/rhythmone.yaml b/static/bidder-info/rhythmone.yaml index 852344db3e3..0ea79afc766 100644 --- a/static/bidder-info/rhythmone.yaml +++ b/static/bidder-info/rhythmone.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[RX_UUID]" diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index 12744fdc75e..8d5ad3f03fb 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -5,3 +5,8 @@ capabilities: site: mediaTypes: - banner +userSync: + # rtbhouse supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml index 0f19ddb9627..6d1568d6b62 100644 --- a/static/bidder-info/rubicon.yaml +++ b/static/bidder-info/rubicon.yaml @@ -9,3 +9,8 @@ capabilities: mediaTypes: - banner - video +userSync: + # rubicon supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/sa_lunamedia.yaml b/static/bidder-info/sa_lunamedia.yaml index 181e1fd6c73..f5c2cf78e20 100644 --- a/static/bidder-info/sa_lunamedia.yaml +++ b/static/bidder-info/sa_lunamedia.yaml @@ -11,4 +11,8 @@ capabilities: mediaTypes: - banner - video - - native \ No newline at end of file + - native +userSync: + redirect: + url: "https://cookie.lmgssp.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/sharethrough.yaml b/static/bidder-info/sharethrough.yaml index 45dceb94d22..12c9c4bc833 100644 --- a/static/bidder-info/sharethrough.yaml +++ b/static/bidder-info/sharethrough.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - native - banner +userSync: + redirect: + url: "https://match.sharethrough.com/FGMrCMMc/v1?redirectUri={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/smartadserver.yaml b/static/bidder-info/smartadserver.yaml index f22c7149ff7..a6b06d95aa7 100644 --- a/static/bidder-info/smartadserver.yaml +++ b/static/bidder-info/smartadserver.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://ssbsync-global.smartadserver.com/api/sync?callerId=5&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri={{.RedirectURL}}" + userMacro: "[ssb_sync_pid]" diff --git a/static/bidder-info/smarthub.yaml b/static/bidder-info/smarthub.yaml index bfee9490840..6932780f195 100644 --- a/static/bidder-info/smarthub.yaml +++ b/static/bidder-info/smarthub.yaml @@ -11,3 +11,8 @@ capabilities: - banner - video - native +userSync: + # smarthub supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/smartrtb.yaml b/static/bidder-info/smartrtb.yaml index c26184f91b7..d73afe0440c 100644 --- a/static/bidder-info/smartrtb.yaml +++ b/static/bidder-info/smartrtb.yaml @@ -9,3 +9,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://market-global.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&rr={{.RedirectURL}}" + userMacro: "{XID}" diff --git a/static/bidder-info/smartyads.yaml b/static/bidder-info/smartyads.yaml index df4c1b7ffb5..2704f007ca3 100644 --- a/static/bidder-info/smartyads.yaml +++ b/static/bidder-info/smartyads.yaml @@ -11,4 +11,8 @@ capabilities: - banner - video - native +userSync: + redirect: + url: "https://as.ck-ie.com/prebid.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/smilewanted.yaml b/static/bidder-info/smilewanted.yaml index 81b0585bb5e..e3d62bd5db6 100644 --- a/static/bidder-info/smilewanted.yaml +++ b/static/bidder-info/smilewanted.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/somoaudience.yaml b/static/bidder-info/somoaudience.yaml index 83b64e5c58e..4a4813c42a5 100644 --- a/static/bidder-info/somoaudience.yaml +++ b/static/bidder-info/somoaudience.yaml @@ -11,3 +11,7 @@ capabilities: - banner - native - video +userSync: + redirect: + url: "https://publisher-east.mobileadtrading.com/usersync?ru={{.RedirectURL}}" + userMacro: "${UID}" diff --git a/static/bidder-info/sonobi.yaml b/static/bidder-info/sonobi.yaml index 22a0a158306..9961d322083 100644 --- a/static/bidder-info/sonobi.yaml +++ b/static/bidder-info/sonobi.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://sync.go.sonobi.com/us.gif?loc={{.RedirectURL}}" + userMacro: "[UID]" diff --git a/static/bidder-info/sovrn.yaml b/static/bidder-info/sovrn.yaml index 4c6251bdf57..4741c8a19fe 100644 --- a/static/bidder-info/sovrn.yaml +++ b/static/bidder-info/sovrn.yaml @@ -8,3 +8,7 @@ capabilities: site: mediaTypes: - banner +userSync: + redirect: + url: "https://ap.lijit.com/pixel?redir={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/synacormedia.yaml b/static/bidder-info/synacormedia.yaml index 33086cd8e6c..7ca12d5323a 100644 --- a/static/bidder-info/synacormedia.yaml +++ b/static/bidder-info/synacormedia.yaml @@ -9,3 +9,12 @@ capabilities: mediaTypes: - banner - video +userSync: + iframe: + url: "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb={{.RedirectURL}}" + userMacro: "[USER_ID]" +userSync: + # synacormedia supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe \ No newline at end of file diff --git a/static/bidder-info/tappx.yaml b/static/bidder-info/tappx.yaml index eb655aa6a0c..6a321af0727 100644 --- a/static/bidder-info/tappx.yaml +++ b/static/bidder-info/tappx.yaml @@ -9,4 +9,8 @@ capabilities: site: mediaTypes: - banner - - video \ No newline at end of file + - video +userSync: + iframe: + url: "https://ssp.api.tappx.com/cs/usersync.php?gdpr_optin={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&type=iframe&ruid={{.RedirectURL}}" + userMacro: "{{TPPXUID}}" diff --git a/static/bidder-info/telaria.yaml b/static/bidder-info/telaria.yaml index 736fb9720b3..7d9501dc086 100644 --- a/static/bidder-info/telaria.yaml +++ b/static/bidder-info/telaria.yaml @@ -8,3 +8,7 @@ capabilities: site: mediaTypes: - video +userSync: + redirect: + url: "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir={{.RedirectURL}}" + userMacro: "[tvid]" diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml index fe0ad8b2203..99026b6f171 100644 --- a/static/bidder-info/triplelift.yaml +++ b/static/bidder-info/triplelift.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml index 40d9be8f294..32eb07efa14 100644 --- a/static/bidder-info/triplelift_native.yaml +++ b/static/bidder-info/triplelift_native.yaml @@ -8,3 +8,7 @@ capabilities: site: mediaTypes: - native +userSync: + redirect: + url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/trustx.yaml b/static/bidder-info/trustx.yaml index 6cfdd9fa465..b6c13bc7000 100644 --- a/static/bidder-info/trustx.yaml +++ b/static/bidder-info/trustx.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://x.bidswitch.net/check_uuid/{{.RedirectURL}}?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}" + userMacro: "${BSW_UUID}" diff --git a/static/bidder-info/ucfunnel.yaml b/static/bidder-info/ucfunnel.yaml index e6be68a0261..ca366c395c6 100644 --- a/static/bidder-info/ucfunnel.yaml +++ b/static/bidder-info/ucfunnel.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "SspCookieUserId" diff --git a/static/bidder-info/unruly.yaml b/static/bidder-info/unruly.yaml index e31ea600b3e..ebefcb51c7b 100644 --- a/static/bidder-info/unruly.yaml +++ b/static/bidder-info/unruly.yaml @@ -8,3 +8,7 @@ capabilities: app: mediaTypes: - video +userSync: + iframe: + url: "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/valueimpression.yaml b/static/bidder-info/valueimpression.yaml index 1d64abcb68f..0782f897f2a 100644 --- a/static/bidder-info/valueimpression.yaml +++ b/static/bidder-info/valueimpression.yaml @@ -9,3 +9,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/verizonmedia.yaml b/static/bidder-info/verizonmedia.yaml index 0b4642fcb6a..80db16ed28b 100644 --- a/static/bidder-info/verizonmedia.yaml +++ b/static/bidder-info/verizonmedia.yaml @@ -8,3 +8,8 @@ capabilities: site: mediaTypes: - banner +userSync: + # verizonmedia supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/viewdeos.yaml b/static/bidder-info/viewdeos.yaml index 9483e281de0..bf0c1789fe3 100644 --- a/static/bidder-info/viewdeos.yaml +++ b/static/bidder-info/viewdeos.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + # viewdeos supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/visx.yaml b/static/bidder-info/visx.yaml index 789ce478bea..77d4463ac7a 100644 --- a/static/bidder-info/visx.yaml +++ b/static/bidder-info/visx.yaml @@ -8,3 +8,7 @@ capabilities: app: mediaTypes: - banner +userSync: + redirect: + url: "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: "${UUID}" diff --git a/static/bidder-info/vrtcal.yaml b/static/bidder-info/vrtcal.yaml index fcf9294adcb..9a33e9f941a 100644 --- a/static/bidder-info/vrtcal.yaml +++ b/static/bidder-info/vrtcal.yaml @@ -4,3 +4,8 @@ capabilities: app: mediaTypes: - banner +userSync: + # vrtcal supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-info/yieldlab.yaml b/static/bidder-info/yieldlab.yaml index 3030d8a1d42..14960089cd1 100644 --- a/static/bidder-info/yieldlab.yaml +++ b/static/bidder-info/yieldlab.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r={{.RedirectURL}}" + userMacro: "%%YL_UID%%" diff --git a/static/bidder-info/yieldmo.yaml b/static/bidder-info/yieldmo.yaml index b1385acbebc..c5feacc83b9 100644 --- a/static/bidder-info/yieldmo.yaml +++ b/static/bidder-info/yieldmo.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/yieldone.yaml b/static/bidder-info/yieldone.yaml index 74aef46d24f..46215b5e8ec 100644 --- a/static/bidder-info/yieldone.yaml +++ b/static/bidder-info/yieldone.yaml @@ -9,3 +9,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://y.one.impact-ad.jp/hbs_cs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-info/zeroclickfraud.yaml b/static/bidder-info/zeroclickfraud.yaml index 527c0065600..8f7149ea137 100644 --- a/static/bidder-info/zeroclickfraud.yaml +++ b/static/bidder-info/zeroclickfraud.yaml @@ -11,3 +11,7 @@ capabilities: - banner - native - video +userSync: + iframe: + url: "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "${uid}" diff --git a/usersync/bidderchooser.go b/usersync/bidderchooser.go new file mode 100644 index 00000000000..1b01e5116e9 --- /dev/null +++ b/usersync/bidderchooser.go @@ -0,0 +1,60 @@ +package usersync + +// bidderChooser determines which bidders to consider for user syncing. +type bidderChooser interface { + // choose returns an ordered collection of potentially non-unique bidders. + choose(requested, available []string, cooperative Cooperative) []string +} + +// standardBidderChooser implements the bidder choosing algorithm per official Prebid specification. +type standardBidderChooser struct { + shuffler shuffler +} + +func (c standardBidderChooser) choose(requested, available []string, cooperative Cooperative) []string { + if cooperative.Enabled { + return c.chooseCooperative(requested, available, cooperative.PriorityGroups) + } + + if len(requested) == 0 { + return c.shuffledCopy(available) + } + + return c.shuffledCopy(requested) +} + +func (c standardBidderChooser) chooseCooperative(requested, available []string, priorityGroups [][]string) []string { + // allocate enough memory for the slice to try to avoid re-allocation. the 50% overhead is a guess + // at a satisfactory value. since all available bidders are included in the slice, along with + // requested and prioritized bidders, expect there to be be many duplicates. the duplicate are + // resolved in the upstream chooser algorithm. + biddersCapacity := int(float64(len(available)) * 1.5) + bidders := make([]string, 0, biddersCapacity) + + // requested + bidders = c.shuffledAppend(bidders, requested) + + // priority groups + for _, group := range priorityGroups { + bidders = c.shuffledAppend(bidders, group) + } + + // available + bidders = c.shuffledAppend(bidders, available) + + return bidders +} + +func (c standardBidderChooser) shuffledCopy(a []string) []string { + aCopy := make([]string, len(a)) + copy(aCopy, a) + c.shuffler.shuffle(aCopy) + return aCopy +} + +func (c standardBidderChooser) shuffledAppend(a, b []string) []string { + startIndex := len(a) + a = append(a, b...) + c.shuffler.shuffle(a[startIndex:]) + return a +} diff --git a/usersync/bidderchooser_test.go b/usersync/bidderchooser_test.go new file mode 100644 index 00000000000..fe7d296f1f0 --- /dev/null +++ b/usersync/bidderchooser_test.go @@ -0,0 +1,277 @@ +package usersync + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// These tests use a deterministic version of the shuffler, where the order +// is reversed rather than randomized. Most tests specify at least 2 elements +// to implicitly verify the shuffler is invoked (or not invoked). + +func TestBidderChooserChoose(t *testing.T) { + shuffler := reverseShuffler{} + available := []string{"a1", "a2"} + + testCases := []struct { + description string + givenRequested []string + givenCooperative Cooperative + expected []string + }{ + { + description: "No Coop - Nil", + givenRequested: nil, + givenCooperative: Cooperative{Enabled: false}, + expected: []string{"a2", "a1"}, + }, + { + description: "No Coop - Empty", + givenRequested: []string{}, + givenCooperative: Cooperative{Enabled: false}, + expected: []string{"a2", "a1"}, + }, + { + description: "No Coop - One", + givenRequested: []string{"r"}, + givenCooperative: Cooperative{Enabled: false}, + expected: []string{"r"}, + }, + { + description: "No Coop - Many", + givenRequested: []string{"r1", "r2"}, + givenCooperative: Cooperative{Enabled: false}, + expected: []string{"r2", "r1"}, + }, + { + description: "Coop - Nil", + givenRequested: nil, + givenCooperative: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}}}, + expected: []string{"pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"}, + }, + { + description: "Coop - Empty", + givenRequested: nil, + givenCooperative: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}}}, + expected: []string{"pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"}, + }, + { + description: "Coop - Integration Test", + givenRequested: []string{"r1", "r2"}, + givenCooperative: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}}}, + expected: []string{"r2", "r1", "pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"}, + }, + } + + for _, test := range testCases { + chooser := standardBidderChooser{shuffler: shuffler} + result := chooser.choose(test.givenRequested, available, test.givenCooperative) + + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestBidderChooserCooperative(t *testing.T) { + shuffler := reverseShuffler{} + available := []string{"a1", "a2"} + + testCases := []struct { + description string + givenRequested []string + givenPriorityGroups [][]string + expected []string + }{ + { + description: "Nil", + givenRequested: nil, + givenPriorityGroups: nil, + expected: []string{"a2", "a1"}, + }, + { + description: "Empty", + givenRequested: []string{}, + givenPriorityGroups: [][]string{}, + expected: []string{"a2", "a1"}, + }, + { + description: "Requested", + givenRequested: []string{"r1", "r2"}, + givenPriorityGroups: nil, + expected: []string{"r2", "r1", "a2", "a1"}, + }, + { + description: "Priority Groups - One", + givenRequested: nil, + givenPriorityGroups: [][]string{{"pr1A", "pr1B"}}, + expected: []string{"pr1B", "pr1A", "a2", "a1"}, + }, + { + description: "Priority Groups - Many", + givenRequested: nil, + givenPriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}}, + expected: []string{"pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"}, + }, + { + description: "Requested + Priority Groups", + givenRequested: []string{"r1", "r2"}, + givenPriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}}, + expected: []string{"r2", "r1", "pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"}, + }, + } + + for _, test := range testCases { + chooser := standardBidderChooser{shuffler: shuffler} + result := chooser.chooseCooperative(test.givenRequested, available, test.givenPriorityGroups) + + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestBidderChooserShuffledCopy(t *testing.T) { + shuffler := reverseShuffler{} + + testCases := []struct { + description string + given []string + expected []string + }{ + { + description: "Empty", + given: []string{}, + expected: []string{}, + }, + { + description: "One", + given: []string{"a"}, + expected: []string{"a"}, + }, + { + description: "Many", + given: []string{"a", "b"}, + expected: []string{"b", "a"}, + }, + } + + for _, test := range testCases { + givenCopy := copySlice(test.given) + + chooser := standardBidderChooser{shuffler: shuffler} + result := chooser.shuffledCopy(test.given) + + assert.Equal(t, givenCopy, test.given, test.description+":input") + assert.Equal(t, test.expected, result, test.description+":result") + } +} + +func TestBidderChooserShuffledAppend(t *testing.T) { + shuffler := reverseShuffler{} + + testCases := []struct { + description string + givenA []string + givenB []string + expected []string + }{ + { + description: "Empty - Append Nil", + givenA: []string{}, + givenB: nil, + expected: []string{}, + }, + { + description: "Empty - Append Empty", + givenA: []string{}, + givenB: []string{}, + expected: []string{}, + }, + { + description: "Empty - Append One", + givenA: []string{}, + givenB: []string{"b"}, + expected: []string{"b"}, + }, + { + description: "Empty - Append Many", + givenA: []string{}, + givenB: []string{"b1", "b2"}, + expected: []string{"b2", "b1"}, + }, + { + description: "One - Append Nil", + givenA: []string{"a"}, + givenB: nil, + expected: []string{"a"}, + }, + { + description: "One - Append Empty", + givenA: []string{"a"}, + givenB: []string{}, + expected: []string{"a"}, + }, + { + description: "One - Append One", + givenA: []string{"a1"}, + givenB: []string{"b1"}, + expected: []string{"a1", "b1"}, + }, + { + description: "One - Append Many", + givenA: []string{"a1"}, + givenB: []string{"b1", "b2"}, + expected: []string{"a1", "b2", "b1"}, + }, + { + description: "Many - Append Nil", + givenA: []string{"a1", "a2"}, + givenB: nil, + expected: []string{"a1", "a2"}, + }, + { + description: "Many - Append Empty", + givenA: []string{"a1", "a2"}, + givenB: []string{}, + expected: []string{"a1", "a2"}, + }, + { + description: "Many - Append One", + givenA: []string{"a1", "a2"}, + givenB: []string{"b"}, + expected: []string{"a1", "a2", "b"}, + }, + { + description: "Many - Append Many", + givenA: []string{"a1", "a2"}, + givenB: []string{"b1", "b2"}, + expected: []string{"a1", "a2", "b2", "b1"}, + }, + } + + for _, test := range testCases { + givenBCopy := copySlice(test.givenB) + + chooser := standardBidderChooser{shuffler: shuffler} + result := chooser.shuffledAppend(test.givenA, test.givenB) + + assert.Equal(t, givenBCopy, test.givenB, test.description+":input") + assert.Equal(t, test.expected, result, test.description+":result") + } +} + +// copySlice returns a cloned a slice or nil. +func copySlice(a []string) []string { + var aCopy []string + if a != nil { + aCopy = make([]string, len(a)) + copy(aCopy, a) + } + return aCopy +} + +type reverseShuffler struct{} + +func (reverseShuffler) shuffle(a []string) { + for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { + a[i], a[j] = a[j], a[i] + } +} diff --git a/usersync/bidderfilter.go b/usersync/bidderfilter.go new file mode 100644 index 00000000000..2d7d16ffe2b --- /dev/null +++ b/usersync/bidderfilter.go @@ -0,0 +1,62 @@ +package usersync + +// BidderFilter determines if a bidder has permission to perform a user sync activity. +type BidderFilter interface { + // Allowed returns true if the filter determines the bidder has permission and false if either + // the bidder does not have permission or if the filter has an invalid mode. + Allowed(bidder string) bool +} + +// BidderFilterMode represents the inclusion mode of a BidderFilter. +type BidderFilterMode int + +const ( + BidderFilterModeInclude BidderFilterMode = iota + BidderFilterModeExclude +) + +// SpecificBidderFilter implements the BidderFilter which applies the same mode for a list of bidders. +type SpecificBidderFilter struct { + biddersLookup map[string]struct{} + mode BidderFilterMode +} + +// Allowed returns true if the bidder is specified and the mode is include or if the bidder is not specified +// and the mode is exclude and returns false in the opposite cases or when the mode is invalid. +func (f SpecificBidderFilter) Allowed(bidder string) bool { + _, exists := f.biddersLookup[bidder] + + switch f.mode { + case BidderFilterModeInclude: + return exists + case BidderFilterModeExclude: + return !exists + default: + return false + } +} + +// NewSpecificBidderFilter returns a new instance of the NewSpecificBidderFilter filter. +func NewSpecificBidderFilter(bidders []string, mode BidderFilterMode) BidderFilter { + biddersLookup := make(map[string]struct{}, len(bidders)) + for _, bidder := range bidders { + biddersLookup[bidder] = struct{}{} + } + + return SpecificBidderFilter{biddersLookup: biddersLookup, mode: mode} +} + +// UniformBidderFilter implements the BidderFilter interface which applies the same mode for all bidders. +type UniformBidderFilter struct { + mode BidderFilterMode +} + +// Allowed returns true if the mode is include and false if the mode is either exclude or invalid. +func (f UniformBidderFilter) Allowed(bidder string) bool { + return f.mode == BidderFilterModeInclude +} + +// NewUniformBidderFilter returns a new instance of the UniformBidderFilter filter. +func NewUniformBidderFilter(mode BidderFilterMode) BidderFilter { + return UniformBidderFilter{mode: mode} +} diff --git a/usersync/bidderfilter_test.go b/usersync/bidderfilter_test.go new file mode 100644 index 00000000000..ddc757339a2 --- /dev/null +++ b/usersync/bidderfilter_test.go @@ -0,0 +1,104 @@ +package usersync + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSpecificBidderFilter(t *testing.T) { + bidder := "a" + + testCases := []struct { + description string + bidders []string + mode BidderFilterMode + expected bool + }{ + { + description: "Include - None", + bidders: []string{}, + mode: BidderFilterModeInclude, + expected: false, + }, + { + description: "Include - One", + bidders: []string{bidder}, + mode: BidderFilterModeInclude, + expected: true, + }, + { + description: "Include - Many", + bidders: []string{"other", bidder}, + mode: BidderFilterModeInclude, + expected: true, + }, + { + description: "Include - Other", + bidders: []string{"other"}, + mode: BidderFilterModeInclude, + expected: false, + }, + { + description: "Exclude - None", + bidders: []string{}, + mode: BidderFilterModeExclude, + expected: true, + }, + { + description: "Exclude - One", + bidders: []string{bidder}, + mode: BidderFilterModeExclude, + expected: false, + }, + { + description: "Exclude - Many", + bidders: []string{"other", bidder}, + mode: BidderFilterModeExclude, + expected: false, + }, + { + description: "Exclude - Other", + bidders: []string{"other"}, + mode: BidderFilterModeExclude, + expected: true, + }, + { + description: "Invalid Mode", + bidders: []string{bidder}, + mode: BidderFilterMode(-1), + expected: false, + }, + } + + for _, test := range testCases { + filter := NewSpecificBidderFilter(test.bidders, test.mode) + assert.Equal(t, test.expected, filter.Allowed(bidder), test.description) + } +} + +func TestUniformBidderFilter(t *testing.T) { + bidder := "a" + + testCases := []struct { + description string + mode BidderFilterMode + expected bool + }{ + { + description: "Include", + mode: BidderFilterModeInclude, + expected: true, + }, + { + description: "Exclude", + mode: BidderFilterModeExclude, + expected: false, + }, + } + + for _, test := range testCases { + filter := NewUniformBidderFilter(test.mode) + assert.Equal(t, test.expected, filter.Allowed(bidder), test.description) + } +} diff --git a/usersync/chooser.go b/usersync/chooser.go new file mode 100644 index 00000000000..d371846364a --- /dev/null +++ b/usersync/chooser.go @@ -0,0 +1,163 @@ +package usersync + +// Chooser determines which syncers are eligible for a given request. +type Chooser interface { + // Choose considers bidders to sync, filters the bidders, and returns the result of the + // user sync selection. + Choose(request Request, cookie *Cookie) Result +} + +// NewChooser returns a new instance of the standard chooser implementation. +func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { + bidders := make([]string, 0, len(bidderSyncerLookup)) + for k := range bidderSyncerLookup { + bidders = append(bidders, k) + } + + return standardChooser{ + bidderSyncerLookup: bidderSyncerLookup, + biddersAvailable: bidders, + bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, + } +} + +// Request specifies a user sync request. +type Request struct { + Bidders []string + Cooperative Cooperative + Limit int + Privacy Privacy + SyncTypeFilter SyncTypeFilter +} + +// Cooperative specifies the settings for cooperative syncing for a given request, where bidders +// other than those used by the publisher are considered for syncing. +type Cooperative struct { + Enabled bool + PriorityGroups [][]string +} + +// Result specifies which bidders were included in the evaluation and which syncers were chosen. +type Result struct { + BiddersEvaluated []BidderEvaluation + Status Status + SyncersChosen []SyncerChoice +} + +// BidderEvaluation specifies which bidders were considered to be synced. +type BidderEvaluation struct { + Bidder string + SyncerKey string + Status Status +} + +// SyncerChoice specifies a syncer chosen. +type SyncerChoice struct { + Bidder string + Syncer Syncer +} + +// Status specifies the result of a sync evaluation. +type Status int + +const ( + // StatusOK specifies user syncing is permitted. + StatusOK Status = iota + + // StatusBlockedByUserOptOut specifies a user's cookie explicitly signals an opt-out. + StatusBlockedByUserOptOut + + // StatusBlockedByGDPR specifies a user's GDPR TCF consent explicitly forbids host cookies + // or specific bidder syncing. + StatusBlockedByGDPR + + // StatusBlockedByCCPA specifies a user's CCPA consent explicitly forbids bidder syncing. + StatusBlockedByCCPA + + // StatusAlreadySynced specifies a user's cookie has an existing non-expired sync for a specific bidder. + StatusAlreadySynced + + // StatusUnknownBidder specifies a requested bidder is unknown to Prebid Server. + StatusUnknownBidder + + // StatusTypeNotSupported specifies a requested sync type is not supported by a specific bidder. + StatusTypeNotSupported + + // StatusDuplicate specifies the bidder is a duplicate or shared a syncer key with another bidder choice. + StatusDuplicate +) + +// Privacy determines which privacy policies will be enforced for a user sync request. +type Privacy interface { + GDPRAllowsHostCookie() bool + GDPRAllowsBidderSync(bidder string) bool + CCPAAllowsBidderSync(bidder string) bool +} + +// standardChooser implements the user syncer algorithm per official Prebid specification. +type standardChooser struct { + bidderSyncerLookup map[string]Syncer + biddersAvailable []string + bidderChooser bidderChooser +} + +// Choose randomly selects user syncers which are permitted by the user's privacy settings and +// which don't already have a valid user sync. +func (c standardChooser) Choose(request Request, cookie *Cookie) Result { + if !cookie.AllowSyncs() { + return Result{Status: StatusBlockedByUserOptOut} + } + + if !request.Privacy.GDPRAllowsHostCookie() { + return Result{Status: StatusBlockedByGDPR} + } + + syncersSeen := make(map[string]struct{}) + limitDisabled := request.Limit <= 0 + + biddersEvaluated := make([]BidderEvaluation, 0) + syncersChosen := make([]SyncerChoice, 0) + + bidders := c.bidderChooser.choose(request.Bidders, c.biddersAvailable, request.Cooperative) + for i := 0; i < len(bidders) && (limitDisabled || len(syncersChosen) < request.Limit); i++ { + syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie) + + biddersEvaluated = append(biddersEvaluated, evaluation) + if evaluation.Status == StatusOK { + syncersChosen = append(syncersChosen, SyncerChoice{Bidder: bidders[i], Syncer: syncer}) + } + } + + return Result{Status: StatusOK, BiddersEvaluated: biddersEvaluated, SyncersChosen: syncersChosen} +} + +func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie) (Syncer, BidderEvaluation) { + syncer, exists := c.bidderSyncerLookup[bidder] + if !exists { + return nil, BidderEvaluation{Bidder: bidder, Status: StatusUnknownBidder} + } + + _, seen := syncersSeen[syncer.Key()] + if seen { + return nil, BidderEvaluation{Bidder: bidder, Status: StatusDuplicate} + } + syncersSeen[syncer.Key()] = struct{}{} + + if !syncer.SupportsType(syncTypeFilter.ForBidder(bidder)) { + return nil, BidderEvaluation{Bidder: bidder, Status: StatusTypeNotSupported} + } + + if cookie.HasLiveSync(syncer.Key()) { + return nil, BidderEvaluation{Bidder: bidder, Status: StatusAlreadySynced} + } + + if !privacy.GDPRAllowsBidderSync(bidder) { + return nil, BidderEvaluation{Bidder: bidder, Status: StatusBlockedByGDPR} + } + + if !privacy.CCPAAllowsBidderSync(bidder) { + return nil, BidderEvaluation{Bidder: bidder, Status: StatusBlockedByCCPA} + } + + return syncer, BidderEvaluation{Bidder: bidder, Status: StatusOK} +} diff --git a/usersync/chooser_test.go b/usersync/chooser_test.go new file mode 100644 index 00000000000..8a129bd4cc8 --- /dev/null +++ b/usersync/chooser_test.go @@ -0,0 +1,402 @@ +package usersync + +import ( + "testing" + "time" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestNewChooser(t *testing.T) { + testCases := []struct { + description string + bidderSyncerLookup map[string]Syncer + expectedBiddersAvailable []string + }{ + { + description: "Nil", + bidderSyncerLookup: nil, + expectedBiddersAvailable: []string{}, + }, + { + description: "Empty", + bidderSyncerLookup: map[string]Syncer{}, + expectedBiddersAvailable: []string{}, + }, + { + description: "One", + bidderSyncerLookup: map[string]Syncer{"a": fakeSyncer{}}, + expectedBiddersAvailable: []string{"a"}, + }, + { + description: "Many", + bidderSyncerLookup: map[string]Syncer{"a": fakeSyncer{}, "b": fakeSyncer{}}, + expectedBiddersAvailable: []string{"a", "b"}, + }, + } + + for _, test := range testCases { + chooser, _ := NewChooser(test.bidderSyncerLookup).(standardChooser) + assert.ElementsMatch(t, test.expectedBiddersAvailable, chooser.biddersAvailable, test.description) + } +} + +func TestChooserChoose(t *testing.T) { + fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} + fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: true} + fakeSyncerC := fakeSyncer{key: "keyC", supportsIFrame: false} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC} + syncerChoiceA := SyncerChoice{Bidder: "a", Syncer: fakeSyncerA} + syncerChoiceB := SyncerChoice{Bidder: "b", Syncer: fakeSyncerB} + syncTypeFilter := SyncTypeFilter{ + IFrame: NewUniformBidderFilter(BidderFilterModeInclude), + Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} + + cooperativeConfig := Cooperative{Enabled: true} + + testCases := []struct { + description string + givenRequest Request + givenChosenBidders []string + givenCookie Cookie + expected Result + }{ + { + description: "Cookie Opt Out", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{optOut: true}, + expected: Result{ + Status: StatusBlockedByUserOptOut, + BiddersEvaluated: nil, + SyncersChosen: nil, + }, + }, + { + description: "GDPR Host Cookie Not Allowed", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: false, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + expected: Result{ + Status: StatusBlockedByGDPR, + BiddersEvaluated: nil, + SyncersChosen: nil, + }, + }, + { + description: "No Bidders", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{}, + givenCookie: Cookie{}, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "One Bidder - Sync", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", Status: StatusOK}}, + SyncersChosen: []SyncerChoice{syncerChoiceA}, + }, + }, + { + description: "One Bidder - No Sync", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"c"}, + givenCookie: Cookie{}, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "c", Status: StatusTypeNotSupported}}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "Many Bidders - All Sync - Limit Disabled With 0", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a", "b"}, + givenCookie: Cookie{}, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", Status: StatusOK}, {Bidder: "b", Status: StatusOK}}, + SyncersChosen: []SyncerChoice{syncerChoiceA, syncerChoiceB}, + }, + }, + { + description: "Many Bidders - All Sync - Limit Disabled With Negative Value", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: -1, + }, + givenChosenBidders: []string{"a", "b"}, + givenCookie: Cookie{}, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", Status: StatusOK}, {Bidder: "b", Status: StatusOK}}, + SyncersChosen: []SyncerChoice{syncerChoiceA, syncerChoiceB}, + }, + }, + { + description: "Many Bidders - Limited Sync", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: 1, + }, + givenChosenBidders: []string{"a", "b"}, + givenCookie: Cookie{}, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", Status: StatusOK}}, + SyncersChosen: []SyncerChoice{syncerChoiceA}, + }, + }, + { + description: "Many Bidders - Limited Sync - Disqualified Syncers Don't Count Towards Limit", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: 1, + }, + givenChosenBidders: []string{"c", "a", "b"}, + givenCookie: Cookie{}, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "c", Status: StatusTypeNotSupported}, {Bidder: "a", Status: StatusOK}}, + SyncersChosen: []SyncerChoice{syncerChoiceA}, + }, + }, + { + description: "Many Bidders - Some Sync, Some Don't", + givenRequest: Request{ + Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a", "c"}, + givenCookie: Cookie{}, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", Status: StatusOK}, {Bidder: "c", Status: StatusTypeNotSupported}}, + SyncersChosen: []SyncerChoice{syncerChoiceA}, + }, + }, + } + + bidders := []string{"anyRequested"} + biddersAvailable := []string{"anyAvailable"} + for _, test := range testCases { + // set request values which don't need to be specified for each test case + test.givenRequest.Bidders = bidders + test.givenRequest.SyncTypeFilter = syncTypeFilter + test.givenRequest.Cooperative = cooperativeConfig + + mockBidderChooser := &mockBidderChooser{} + mockBidderChooser. + On("choose", test.givenRequest.Bidders, biddersAvailable, cooperativeConfig). + Return(test.givenChosenBidders) + + chooser := standardChooser{ + bidderSyncerLookup: bidderSyncerLookup, + biddersAvailable: biddersAvailable, + bidderChooser: mockBidderChooser, + } + + result := chooser.Choose(test.givenRequest, &test.givenCookie) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestChooserEvaluate(t *testing.T) { + fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} + fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: false} + bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB} + syncTypeFilter := SyncTypeFilter{ + IFrame: NewUniformBidderFilter(BidderFilterModeInclude), + Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} + + cookieNeedsSync := Cookie{} + cookieAlreadyHasSyncForA := Cookie{uids: map[string]uidWithExpiry{"keyA": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} + cookieAlreadyHasSyncForB := Cookie{uids: map[string]uidWithExpiry{"keyB": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} + + testCases := []struct { + description string + givenBidder string + givenSyncersSeen map[string]struct{} + givenPrivacy Privacy + givenCookie Cookie + expectedSyncer Syncer + expectedBidder string + expectedStatus Status + }{ + { + description: "Valid", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + givenCookie: cookieNeedsSync, + expectedSyncer: fakeSyncerA, + expectedBidder: "a", + expectedStatus: StatusOK, + }, + { + description: "Unknown Bidder", + givenBidder: "unknown", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + givenCookie: cookieNeedsSync, + expectedSyncer: nil, + expectedBidder: "unknown", + expectedStatus: StatusUnknownBidder, + }, + { + description: "Duplicate Syncer", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{"keyA": {}}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + givenCookie: cookieNeedsSync, + expectedSyncer: nil, + expectedBidder: "a", + expectedStatus: StatusDuplicate, + }, + { + description: "Incompatible Kind", + givenBidder: "b", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + givenCookie: cookieNeedsSync, + expectedSyncer: nil, + expectedBidder: "b", + expectedStatus: StatusTypeNotSupported, + }, + { + description: "Already Synced", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + givenCookie: cookieAlreadyHasSyncForA, + expectedSyncer: nil, + expectedBidder: "a", + expectedStatus: StatusAlreadySynced, + }, + { + description: "Different Bidder Already Synced", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true}, + givenCookie: cookieAlreadyHasSyncForB, + expectedSyncer: fakeSyncerA, + expectedBidder: "a", + expectedStatus: StatusOK, + }, + { + description: "Blocked By GDPR", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true}, + givenCookie: cookieNeedsSync, + expectedSyncer: nil, + expectedBidder: "a", + expectedStatus: StatusBlockedByGDPR, + }, + { + description: "Blocked By CCPA", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false}, + givenCookie: cookieNeedsSync, + expectedSyncer: nil, + expectedBidder: "a", + expectedStatus: StatusBlockedByCCPA, + }, + } + + for _, test := range testCases { + chooser, _ := NewChooser(bidderSyncerLookup).(standardChooser) + sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, syncTypeFilter, test.givenPrivacy, &test.givenCookie) + + assert.Equal(t, test.expectedSyncer, sync, test.description+":syncer") + + expectedEvaluation := BidderEvaluation{Bidder: test.expectedBidder, Status: test.expectedStatus} + assert.Equal(t, expectedEvaluation, evaluation, test.description+":evaluation") + } +} + +type mockBidderChooser struct { + mock.Mock +} + +func (m *mockBidderChooser) choose(requested, available []string, cooperative Cooperative) []string { + args := m.Called(requested, available, cooperative) + return args.Get(0).([]string) +} + +type fakeSyncer struct { + key string + supportsIFrame bool + supportsRedirect bool +} + +func (s fakeSyncer) Key() string { + return s.key +} + +func (s fakeSyncer) DefaultSyncType() SyncType { + return SyncTypeIFrame +} + +func (s fakeSyncer) SupportsType(syncTypes []SyncType) bool { + for _, syncType := range syncTypes { + if syncType == SyncTypeIFrame && s.supportsIFrame { + return true + } + if syncType == SyncTypeRedirect && s.supportsRedirect { + return true + } + } + return false +} + +func (fakeSyncer) GetSync(syncTypes []SyncType, privacyPolicies privacy.Policies) (Sync, error) { + return Sync{}, nil +} + +type fakePrivacy struct { + gdprAllowsHostCookie bool + gdprAllowsBidderSync bool + ccpaAllowsBidderSync bool +} + +func (p fakePrivacy) GDPRAllowsHostCookie() bool { + return p.gdprAllowsHostCookie +} + +func (p fakePrivacy) GDPRAllowsBidderSync(bidder string) bool { + return p.gdprAllowsBidderSync +} + +func (p fakePrivacy) CCPAAllowsBidderSync(bidder string) bool { + return p.ccpaAllowsBidderSync +} diff --git a/usersync/cookie.go b/usersync/cookie.go index c4e329b65e0..5732e6c4c31 100644 --- a/usersync/cookie.go +++ b/usersync/cookie.go @@ -12,30 +12,17 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const ( - // DEFAULT_TTL is the default amount of time which a cookie is considered valid. - DEFAULT_TTL = 14 * 24 * time.Hour - UID_COOKIE_NAME = "uids" - SameSiteCookieName = "SSCookie" - SameSiteCookieValue = "1" - SameSiteAttribute = "; SameSite=None" -) - -// customBidderTTLs stores rules about how long a particular UID sync is valid for each bidder. -// If a bidder does a cookie sync *without* listing a rule here, then the DEFAULT_TTL will be used. -var customBidderTTLs = map[string]time.Duration{} +const uidCookieName = "uids" -// bidderToFamilyNames maps the BidderName to Adapter.Name() for the early adapters. -// If a mapping isn't listed here, then we assume that the two are the same. -var bidderToFamilyNames = map[openrtb_ext.BidderName]string{ - openrtb_ext.BidderAppnexus: "adnxs", -} +// uidTTL is the default amount of time a uid stored within a cookie is considered valid. This is +// separate from the cookie ttl. +const uidTTL = 14 * 24 * time.Hour -// PBSCookie is the cookie used in Prebid Server. +// Cookie is the cookie used in Prebid Server. // -// To get an instance of this from a request, use ParsePBSCookieFromRequest. +// To get an instance of this from a request, use ParseCookieFromRequest. // To write an instance onto a response, use SetCookieOnResponse. -type PBSCookie struct { +type Cookie struct { uids map[string]uidWithExpiry optOut bool birthday *time.Time @@ -50,22 +37,22 @@ type uidWithExpiry struct { Expires time.Time `json:"expires"` } -// ParsePBSCookieFromRequest parses the UserSyncMap from an HTTP Request. -func ParsePBSCookieFromRequest(r *http.Request, cookie *config.HostCookie) *PBSCookie { +// ParseCookieFromRequest parses the UserSyncMap from an HTTP Request. +func ParseCookieFromRequest(r *http.Request, cookie *config.HostCookie) *Cookie { if cookie.OptOutCookie.Name != "" { optOutCookie, err1 := r.Cookie(cookie.OptOutCookie.Name) if err1 == nil && optOutCookie.Value == cookie.OptOutCookie.Value { - pc := NewPBSCookie() - pc.SetPreference(false) + pc := NewCookie() + pc.SetOptOut(true) return pc } } - var parsed *PBSCookie - uidCookie, err2 := r.Cookie(UID_COOKIE_NAME) + var parsed *Cookie + uidCookie, err2 := r.Cookie(uidCookieName) if err2 == nil { - parsed = ParsePBSCookie(uidCookie) + parsed = ParseCookie(uidCookie) } else { - parsed = NewPBSCookie() + parsed = NewCookie() } // Fixes #582 if uid, _, _ := parsed.GetUID(cookie.Family); uid == "" && cookie.CookieName != "" { @@ -76,76 +63,67 @@ func ParsePBSCookieFromRequest(r *http.Request, cookie *config.HostCookie) *PBSC return parsed } -// ParsePBSCookie parses the UserSync cookie from a raw HTTP cookie. -func ParsePBSCookie(uidCookie *http.Cookie) *PBSCookie { - pc := NewPBSCookie() - - j, err := base64.URLEncoding.DecodeString(uidCookie.Value) +// ParseCookie parses the UserSync cookie from a raw HTTP cookie. +func ParseCookie(httpCookie *http.Cookie) *Cookie { + jsonValue, err := base64.URLEncoding.DecodeString(httpCookie.Value) if err != nil { // corrupted cookie; we should reset - return pc + return NewCookie() } - err = json.Unmarshal(j, pc) - - // The error on Unmarshal here isn't terribly important. - // If the cookie has been corrupted, we should reset to an empty one anyway. - return pc -} -// NewPBSCookie returns an empty PBSCookie -func NewPBSCookie() *PBSCookie { - return &PBSCookie{ - uids: make(map[string]uidWithExpiry), - birthday: timestamp(), + var cookie Cookie + if err = json.Unmarshal(jsonValue, &cookie); err != nil { + // corrupted cookie; we should reset + return NewCookie() } + + return &cookie } -// NewPBSCookie returns an empty PBSCookie with optOut enabled -func NewPBSCookieWithOptOut() *PBSCookie { - return &PBSCookie{ +// NewCookie returns a new empty cookie. +func NewCookie() *Cookie { + return &Cookie{ uids: make(map[string]uidWithExpiry), - optOut: true, birthday: timestamp(), } } // AllowSyncs is true if the user lets bidders sync cookies, and false otherwise. -func (cookie *PBSCookie) AllowSyncs() bool { +func (cookie *Cookie) AllowSyncs() bool { return cookie != nil && !cookie.optOut } -// SetPreference is used to change whether or not we're allowed to sync cookies for this user. -func (cookie *PBSCookie) SetPreference(allow bool) { - if allow { - cookie.optOut = false - } else { - cookie.optOut = true +// SetOptOut is used to change whether or not we're allowed to sync cookies for this user. +func (cookie *Cookie) SetOptOut(optOut bool) { + cookie.optOut = optOut + + if optOut { cookie.uids = make(map[string]uidWithExpiry) } } // Gets an HTTP cookie containing all the data from this UserSyncMap. This is a snapshot--not a live view. -func (cookie *PBSCookie) ToHTTPCookie(ttl time.Duration) *http.Cookie { +func (cookie *Cookie) ToHTTPCookie(ttl time.Duration) *http.Cookie { j, _ := json.Marshal(cookie) b64 := base64.URLEncoding.EncodeToString(j) return &http.Cookie{ - Name: UID_COOKIE_NAME, + Name: uidCookieName, Value: b64, Expires: time.Now().Add(ttl), Path: "/", } } -// GetUID Gets this user's ID for the given family. +// GetUID Gets this user's ID for the given syncer key. // The first returned value is the user's ID. // The second returned value is true if we had a value stored, and false if we didn't. // The third returned value is true if that value is "active", and false if it's expired. // // If no value was stored, then the "isActive" return value will be false. -func (cookie *PBSCookie) GetUID(familyName string) (string, bool, bool) { +func (cookie *Cookie) GetUID(key string) (string, bool, bool) { if cookie != nil { - if uid, ok := cookie.uids[familyName]; ok { + if uid, ok := cookie.uids[key]; ok { return uid.UID, true, time.Now().Before(uid.Expires) } } @@ -153,7 +131,7 @@ func (cookie *PBSCookie) GetUID(familyName string) (string, bool, bool) { } // GetUIDs returns this user's ID for all the bidders -func (cookie *PBSCookie) GetUIDs() map[string]string { +func (cookie *Cookie) GetUIDs() map[string]string { uids := make(map[string]string) if cookie != nil { // Extract just the uid for each bidder @@ -164,18 +142,8 @@ func (cookie *PBSCookie) GetUIDs() map[string]string { return uids } -// GetId wraps GetUID, letting callers fetch the ID given an OpenRTB BidderName. -func (cookie *PBSCookie) GetId(bidderName openrtb_ext.BidderName) (id string, exists bool) { - if familyName, ok := bidderToFamilyNames[bidderName]; ok { - id, exists, _ = cookie.GetUID(familyName) - } else { - id, exists, _ = cookie.GetUID(string(bidderName)) - } - return -} - // SetCookieOnResponse is a shortcut for "ToHTTPCookie(); cookie.setDomain(domain); setCookie(w, cookie)" -func (cookie *PBSCookie) SetCookieOnResponse(w http.ResponseWriter, setSiteCookie bool, cfg *config.HostCookie, ttl time.Duration) { +func (cookie *Cookie) SetCookieOnResponse(w http.ResponseWriter, setSiteCookie bool, cfg *config.HostCookie, ttl time.Duration) { httpCookie := cookie.ToHTTPCookie(ttl) var domain string = cfg.Domain @@ -202,86 +170,70 @@ func (cookie *PBSCookie) SetCookieOnResponse(w http.ResponseWriter, setSiteCooki currSize = len([]byte(httpCookie.String())) } - var uidsCookieStr string - var sameSiteCookie *http.Cookie if setSiteCookie { httpCookie.Secure = true - uidsCookieStr = httpCookie.String() - uidsCookieStr += SameSiteAttribute - sameSiteCookie = &http.Cookie{ - Name: SameSiteCookieName, - Value: SameSiteCookieValue, - Expires: time.Now().Add(ttl), - Path: "/", - Secure: true, - } - sameSiteCookieStr := sameSiteCookie.String() - sameSiteCookieStr += SameSiteAttribute - w.Header().Add("Set-Cookie", sameSiteCookieStr) - } else { - uidsCookieStr = httpCookie.String() + httpCookie.SameSite = http.SameSiteNoneMode } - w.Header().Add("Set-Cookie", uidsCookieStr) + w.Header().Add("Set-Cookie", httpCookie.String()) } -// Unsync removes the user's ID for the given family from this cookie. -func (cookie *PBSCookie) Unsync(familyName string) { - delete(cookie.uids, familyName) +// Unsync removes the user's ID for the given syncer key from this cookie. +func (cookie *Cookie) Unsync(key string) { + delete(cookie.uids, key) } -// HasLiveSync returns true if we have an active UID for the given family, and false otherwise. -func (cookie *PBSCookie) HasLiveSync(familyName string) bool { - _, _, isLive := cookie.GetUID(familyName) +// HasLiveSync returns true if we have an active UID for the given syncer key, and false otherwise. +func (cookie *Cookie) HasLiveSync(key string) bool { + _, _, isLive := cookie.GetUID(key) return isLive } -// LiveSyncCount returns the number of families which have active UIDs for this user. -func (cookie *PBSCookie) LiveSyncCount() int { +// HasAnyLiveSyncs returns true if this cookie has at least one active sync. +func (cookie *Cookie) HasAnyLiveSyncs() bool { now := time.Now() - numSyncs := 0 if cookie != nil { for _, value := range cookie.uids { if now.Before(value.Expires) { - numSyncs++ + return true } } } - return numSyncs + return false } -// TrySync tries to set the UID for some family name. It returns an error if the set didn't happen. -func (cookie *PBSCookie) TrySync(familyName string, uid string) error { +// TrySync tries to set the UID for some syncer key. It returns an error if the set didn't happen. +func (cookie *Cookie) TrySync(key string, uid string) error { if !cookie.AllowSyncs() { - return errors.New("The user has opted out of prebid server PBSCookie syncs.") + return errors.New("The user has opted out of prebid server cookie syncs.") } // At the moment, Facebook calls /setuid with a UID of 0 if the user isn't logged into Facebook. // They shouldn't be sending us a sentinel value... but since they are, we're refusing to save that ID. - if familyName == string(openrtb_ext.BidderAudienceNetwork) && uid == "0" { + if key == string(openrtb_ext.BidderAudienceNetwork) && uid == "0" { return errors.New("audienceNetwork uses a UID of 0 as \"not yet recognized\".") } - cookie.uids[familyName] = uidWithExpiry{ + cookie.uids[key] = uidWithExpiry{ UID: uid, - Expires: getExpiry(familyName), + Expires: time.Now().Add(uidTTL), } return nil } -// pbsCookieJson defines the JSON contract for the cookie data's storage format. +// cookieJson defines the JSON contract for the cookie data's storage format. // -// This exists so that PBSCookie (which is public) can have private fields, and the rest of -// PBS doesn't have to worry about the cookie data storage format. -type pbsCookieJson struct { +// This exists so that Cookie (which is public) can have private fields, and the rest of +// the code doesn't have to worry about the cookie data storage format. +type cookieJson struct { LegacyUIDs map[string]string `json:"uids,omitempty"` UIDs map[string]uidWithExpiry `json:"tempUIDs,omitempty"` OptOut bool `json:"optout,omitempty"` Birthday *time.Time `json:"bday,omitempty"` } -func (cookie *PBSCookie) MarshalJSON() ([]byte, error) { - return json.Marshal(pbsCookieJson{ +func (cookie *Cookie) MarshalJSON() ([]byte, error) { + return json.Marshal(cookieJson{ UIDs: cookie.uids, OptOut: cookie.optOut, Birthday: cookie.birthday, @@ -296,8 +248,8 @@ func (cookie *PBSCookie) MarshalJSON() ([]byte, error) { // This Unmarshal method interprets both data formats, and does some conversions on legacy data to make it current. // If you're seeing this message after March 2018, it's safe to assume that all the legacy cookies have been // updated and remove the legacy logic. -func (cookie *PBSCookie) UnmarshalJSON(b []byte) error { - var cookieContract pbsCookieJson +func (cookie *Cookie) UnmarshalJSON(b []byte) error { + var cookieContract cookieJson err := json.Unmarshal(b, &cookieContract) if err == nil { cookie.optOut = cookieContract.OptOut @@ -337,15 +289,6 @@ func (cookie *PBSCookie) UnmarshalJSON(b []byte) error { return err } -// getExpiry gets an expiry date for the cookie, assuming it was generated right now. -func getExpiry(familyName string) time.Time { - ttl := DEFAULT_TTL - if customTTL, ok := customBidderTTLs[familyName]; ok { - ttl = customTTL - } - return time.Now().Add(ttl) -} - func timestamp() *time.Time { birthday := time.Now() return &birthday diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index ef2e9911e46..f3944a389c3 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -10,12 +10,11 @@ import ( "time" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) func TestOptOutCookie(t *testing.T) { - cookie := &PBSCookie{ + cookie := &Cookie{ uids: make(map[string]uidWithExpiry), optOut: true, birthday: timestamp(), @@ -24,7 +23,7 @@ func TestOptOutCookie(t *testing.T) { } func TestEmptyOptOutCookie(t *testing.T) { - cookie := &PBSCookie{ + cookie := &Cookie{ uids: make(map[string]uidWithExpiry), optOut: true, birthday: timestamp(), @@ -33,7 +32,7 @@ func TestEmptyOptOutCookie(t *testing.T) { } func TestEmptyCookie(t *testing.T) { - cookie := &PBSCookie{ + cookie := &Cookie{ uids: make(map[string]uidWithExpiry, 0), optOut: false, birthday: timestamp(), @@ -48,7 +47,7 @@ func TestCookieWithData(t *testing.T) { func TestBidderNameGets(t *testing.T) { cookie := newSampleCookie() - id, exists := cookie.GetId(openrtb_ext.BidderAppnexus) + id, exists, _ := cookie.GetUID("adnxs") if !exists { t.Errorf("Cookie missing expected Appnexus ID") } @@ -56,7 +55,7 @@ func TestBidderNameGets(t *testing.T) { t.Errorf("Bad appnexus id. Expected %s, got %s", "123", id) } - id, exists = cookie.GetId(openrtb_ext.BidderRubicon) + id, exists, _ = cookie.GetUID("rubicon") if !exists { t.Errorf("Cookie missing expected Rubicon ID") } @@ -66,14 +65,14 @@ func TestBidderNameGets(t *testing.T) { } func TestRejectAudienceNetworkCookie(t *testing.T) { - raw := &PBSCookie{ + raw := &Cookie{ uids: map[string]uidWithExpiry{ "audienceNetwork": newTempId("0", 10), }, optOut: false, birthday: timestamp(), } - parsed := ParsePBSCookie(raw.ToHTTPCookie(90 * 24 * time.Hour)) + parsed := ParseCookie(raw.ToHTTPCookie(90 * 24 * time.Hour)) if parsed.HasLiveSync("audienceNetwork") { t.Errorf("Cookie serializing and deserializing should delete audienceNetwork values of 0") } @@ -90,23 +89,23 @@ func TestRejectAudienceNetworkCookie(t *testing.T) { func TestOptOutReset(t *testing.T) { cookie := newSampleCookie() - cookie.SetPreference(false) + cookie.SetOptOut(true) if cookie.AllowSyncs() { - t.Error("After SetPreference(false), a cookie should not allow more user syncs.") + t.Error("After SetOptOut(true), a cookie should not allow more user syncs.") } ensureConsistency(t, cookie) } func TestOptIn(t *testing.T) { - cookie := &PBSCookie{ + cookie := &Cookie{ uids: make(map[string]uidWithExpiry), optOut: true, birthday: timestamp(), } - cookie.SetPreference(true) + cookie.SetOptOut(false) if !cookie.AllowSyncs() { - t.Error("After SetPreference(true), a cookie should allow more user syncs.") + t.Error("After SetOptOut(false), a cookie should allow more user syncs.") } ensureConsistency(t, cookie) } @@ -116,7 +115,7 @@ func TestParseCorruptedCookie(t *testing.T) { Name: "uids", Value: "bad base64 encoding", } - parsed := ParsePBSCookie(&raw) + parsed := ParseCookie(&raw) ensureEmptyMap(t, parsed) } @@ -126,7 +125,7 @@ func TestParseCorruptedCookieJSON(t *testing.T) { Name: "uids", Value: cookieData, } - parsed := ParsePBSCookie(&raw) + parsed := ParseCookie(&raw) ensureEmptyMap(t, parsed) } @@ -134,10 +133,10 @@ func TestParseNilSyncMap(t *testing.T) { cookieJSON := "{\"bday\":123,\"optout\":true}" cookieData := base64.URLEncoding.EncodeToString([]byte(cookieJSON)) raw := http.Cookie{ - Name: UID_COOKIE_NAME, + Name: uidCookieName, Value: cookieData, } - parsed := ParsePBSCookie(&raw) + parsed := ParseCookie(&raw) ensureEmptyMap(t, parsed) ensureConsistency(t, parsed) } @@ -150,7 +149,7 @@ func TestParseOtherCookie(t *testing.T) { Name: otherCookieName, Value: id, }) - parsed := ParsePBSCookieFromRequest(req, &config.HostCookie{ + parsed := ParseCookieFromRequest(req, &config.HostCookie{ Family: "adnxs", CookieName: otherCookieName, }) @@ -160,6 +159,89 @@ func TestParseOtherCookie(t *testing.T) { } } +func TestParseCookieFromRequestOptOut(t *testing.T) { + optOutCookieName := "optOutCookieName" + optOutCookieValue := "optOutCookieValue" + + existingCookie := *(&Cookie{ + uids: map[string]uidWithExpiry{ + "foo": newTempId("fooID", 1), + "bar": newTempId("barID", 2), + }, + optOut: false, + birthday: timestamp(), + }).ToHTTPCookie(24 * time.Hour) + + testCases := []struct { + description string + givenExistingCookies []http.Cookie + expectedEmpty bool + expectedSetOptOut bool + }{ + { + description: "Opt Out Cookie", + givenExistingCookies: []http.Cookie{ + existingCookie, + {Name: optOutCookieName, Value: optOutCookieValue}}, + expectedEmpty: true, + expectedSetOptOut: true, + }, + { + description: "No Opt Out Cookie", + givenExistingCookies: []http.Cookie{ + existingCookie}, + expectedEmpty: false, + expectedSetOptOut: false, + }, + { + description: "Opt Out Cookie - Wrong Value", + givenExistingCookies: []http.Cookie{ + existingCookie, + {Name: optOutCookieName, Value: "wrong"}}, + expectedEmpty: false, + expectedSetOptOut: false, + }, + { + description: "Opt Out Cookie - Wrong Name", + givenExistingCookies: []http.Cookie{ + existingCookie, + {Name: "wrong", Value: optOutCookieValue}}, + expectedEmpty: false, + expectedSetOptOut: false, + }, + { + description: "Opt Out Cookie - No Host Cookies", + givenExistingCookies: []http.Cookie{ + {Name: optOutCookieName, Value: optOutCookieValue}}, + expectedEmpty: true, + expectedSetOptOut: true, + }, + } + + for _, test := range testCases { + req := httptest.NewRequest("POST", "http://www.prebid.com", nil) + + for _, c := range test.givenExistingCookies { + req.AddCookie(&c) + } + + parsed := ParseCookieFromRequest(req, &config.HostCookie{ + Family: "foo", + OptOutCookie: config.Cookie{ + Name: optOutCookieName, + Value: optOutCookieValue, + }, + }) + + if test.expectedEmpty { + assert.Empty(t, parsed.uids, test.description+":empty") + } else { + assert.NotEmpty(t, parsed.uids, test.description+":not-empty") + } + assert.Equal(t, parsed.optOut, test.expectedSetOptOut, test.description+":opt-out") + } +} + func TestCookieReadWrite(t *testing.T) { cookie := newSampleCookie() @@ -168,22 +250,23 @@ func TestCookieReadWrite(t *testing.T) { if !exists || !isLive || uid != "123" { t.Errorf("Received cookie should have the adnxs ID=123. Got %s", uid) } + uid, exists, isLive = received.GetUID("rubicon") if !exists || !isLive || uid != "456" { t.Errorf("Received cookie should have the rubicon ID=456. Got %s", uid) } - if received.LiveSyncCount() != 2 { - t.Errorf("Expected 2 user syncs. Got %d", received.LiveSyncCount()) - } + + assert.True(t, received.HasAnyLiveSyncs(), "Has Live Syncs") + assert.Len(t, received.uids, 2, "Sync Count") } func TestPopulatedLegacyCookieRead(t *testing.T) { legacyJson := `{"uids":{"adnxs":"123","audienceNetwork":"456"},"bday":"2017-08-03T21:04:52.629198911Z"}` - var cookie PBSCookie + var cookie Cookie json.Unmarshal([]byte(legacyJson), &cookie) - if cookie.LiveSyncCount() != 0 { - t.Errorf("Expected 0 user syncs. Got %d", cookie.LiveSyncCount()) + if cookie.HasAnyLiveSyncs() { + t.Error("Expected 0 user syncs. Found at least 1.") } if cookie.HasLiveSync("adnxs") { t.Errorf("Received cookie should act like it has no ID for adnxs.") @@ -195,22 +278,22 @@ func TestPopulatedLegacyCookieRead(t *testing.T) { func TestEmptyLegacyCookieRead(t *testing.T) { legacyJson := `{"bday":"2017-08-29T18:54:18.393925772Z"}` - var cookie PBSCookie + var cookie Cookie json.Unmarshal([]byte(legacyJson), &cookie) - if cookie.LiveSyncCount() != 0 { - t.Errorf("Expected 0 user syncs. Got %d", cookie.LiveSyncCount()) + if cookie.HasAnyLiveSyncs() { + t.Error("Expected 0 user syncs. Found at least 1.") } } func TestNilCookie(t *testing.T) { - var nilCookie *PBSCookie + var nilCookie *Cookie if nilCookie.HasLiveSync("anything") { t.Error("nil cookies should respond with false when asked if they have a sync") } - if nilCookie.LiveSyncCount() != 0 { + if nilCookie.HasAnyLiveSyncs() { t.Error("nil cookies shouldn't have any syncs.") } @@ -229,15 +312,6 @@ func TestNilCookie(t *testing.T) { if isLive { t.Error("nil cookies shouldn't report live UID mappings.") } - - uid, hadUID = nilCookie.GetId("anything") - - if uid != "" { - t.Error("nil cookies should return empty strings for the UID.") - } - if hadUID { - t.Error("nil cookies shouldn't claim to have a UID mapping.") - } } func TestGetUIDs(t *testing.T) { @@ -250,60 +324,68 @@ func TestGetUIDs(t *testing.T) { } func TestGetUIDsWithEmptyCookie(t *testing.T) { - cookie := &PBSCookie{} + cookie := &Cookie{} uids := cookie.GetUIDs() assert.Len(t, uids, 0, "GetUIDs shouldn't return any user syncs for an empty cookie") } func TestGetUIDsWithNilCookie(t *testing.T) { - var cookie *PBSCookie + var cookie *Cookie uids := cookie.GetUIDs() assert.Len(t, uids, 0, "GetUIDs shouldn't return any user syncs for a nil cookie") } func TestTrimCookiesClosestExpirationDates(t *testing.T) { - cookieToSend, cookieToSendLen := newTestCookie() - closestToExpirationDate := "key7" + cookieToSend := &Cookie{ + uids: map[string]uidWithExpiry{ + "k1": newTempId("12345678901234567890123456789012345678901234567890", 7), + "k2": newTempId("abcdefghijklmnopqrstuvwxyz", 6), + "k3": newTempId("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 6), + "k4": newTempId("12345678901234567890123456789612345678901234567890", 5), + "k5": newTempId("aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ", 4), + "k6": newTempId("12345678901234567890123456789012345678901234567890", 3), + "k7": newTempId("abcdefghijklmnopqrstuvwxyz", 2), + }, + optOut: false, + birthday: timestamp(), + } type aTest struct { maxCookieSize int - expAction string + expKeys []string } testCases := []aTest{ - {maxCookieSize: 2000, expAction: "equal"}, //1 don't trim, set - {maxCookieSize: 0, expAction: "equal"}, //2 unlimited size: don't trim, set - {maxCookieSize: 800, expAction: "trim"}, //3 trim to size and set - {maxCookieSize: 500, expAction: "trim"}, //4 trim to size and set - {maxCookieSize: 200, expAction: "empty"}, //5 insufficient size, trim to zero length and set - {maxCookieSize: -100, expAction: "empty"}, //6 invalid size, trim to zero length and set + {maxCookieSize: 2000, expKeys: []string{"k1", "k2", "k3", "k4", "k5", "k6", "k7"}}, //1 don't trim, set + {maxCookieSize: 0, expKeys: []string{"k1", "k2", "k3", "k4", "k5", "k6", "k7"}}, //2 unlimited size: don't trim, set + {maxCookieSize: 800, expKeys: []string{"k1", "k2", "k3", "k4"}}, //3 trim to size and set + {maxCookieSize: 500, expKeys: []string{"k1", "k3"}}, //4 trim to size and set + {maxCookieSize: 200, expKeys: []string{}}, //5 insufficient size, trim to zero length and set + {maxCookieSize: -100, expKeys: []string{}}, //6 invalid size, trim to zero length and set } for i := range testCases { processedCookie := writeThenRead(cookieToSend, testCases[i].maxCookieSize) - switch testCases[i].expAction { - case "equal": - assert.Equal(t, cookieToSendLen, len(processedCookie.uids), "[Test %d] MaxCookieSizeBytes equal to zero or bigger than %d bytes should be enough to set and remain cookie unchanged \n", i+1, len(processedCookie.uids)) - assert.Containsf(t, processedCookie.uids, closestToExpirationDate, "[Test %d] Oldest entry in cookie should not have been eliminated", i+1) - case "trim": - assert.Equal(t, cookieToSendLen > len(processedCookie.uids), true, "[Test %d] MaxCookieSizeBytes of %d is smaller than %d bytes and cookie entries should have been removed\n", i+1, testCases[i].maxCookieSize, cookieToSendLen) - assert.NotContainsf(t, processedCookie.uids, closestToExpirationDate, "[Test %d] Oldest entry in cookie should not have been eliminated", i+1) - case "empty": - assert.Equal(t, len(processedCookie.uids), 0, "[Test %d] MaxCookieSizeBytes of %d is too small, processedCookie.uids should be empty\n", i+1) + + actualKeys := make([]string, 0, 7) + for key := range processedCookie.uids { + actualKeys = append(actualKeys, key) } + + assert.ElementsMatch(t, testCases[i].expKeys, actualKeys, "[Test %d]", i+1) } } -func ensureEmptyMap(t *testing.T, cookie *PBSCookie) { +func ensureEmptyMap(t *testing.T, cookie *Cookie) { if !cookie.AllowSyncs() { t.Error("Empty cookies should allow user syncs.") } - if cookie.LiveSyncCount() != 0 { - t.Errorf("Empty cookies shouldn't have any user syncs. Found %d.", cookie.LiveSyncCount()) + if cookie.HasAnyLiveSyncs() { + t.Error("Empty cookies shouldn't have any user syncs. Found at least 1.") } } -func ensureConsistency(t *testing.T, cookie *PBSCookie) { +func ensureConsistency(t *testing.T, cookie *Cookie) { if cookie.AllowSyncs() { err := cookie.TrySync("pulsepoint", "1") if err != nil { @@ -330,8 +412,8 @@ func ensureConsistency(t *testing.T, cookie *PBSCookie) { t.Error("PBSCookie.GetUID() should return empty strings if it doesn't have a sync") } } else { - if cookie.LiveSyncCount() != 0 { - t.Errorf("If the user opted out, the PBSCookie should have no user syncs. Got %d", cookie.LiveSyncCount()) + if cookie.HasAnyLiveSyncs() { + t.Error("If the user opted out, the PBSCookie should have no user syncs.") } err := cookie.TrySync("adnxs", "123") @@ -340,13 +422,12 @@ func ensureConsistency(t *testing.T, cookie *PBSCookie) { } } - copiedCookie := ParsePBSCookie(cookie.ToHTTPCookie(90 * 24 * time.Hour)) + copiedCookie := ParseCookie(cookie.ToHTTPCookie(90 * 24 * time.Hour)) if copiedCookie.AllowSyncs() != cookie.AllowSyncs() { t.Error("The PBSCookie interface shouldn't let modifications happen if the user has opted out") } - if cookie.LiveSyncCount() != copiedCookie.LiveSyncCount() { - t.Errorf("Incorrect sync count. Expected %d, got %d", copiedCookie.LiveSyncCount(), cookie.LiveSyncCount()) - } + + assert.Equal(t, len(cookie.uids), len(copiedCookie.uids), "Incorrect sync count on reparsed cookie.") for family, uid := range copiedCookie.uids { if !cookie.HasLiveSync(family) { @@ -372,8 +453,8 @@ func newTempId(uid string, offset int) uidWithExpiry { } } -func newSampleCookie() *PBSCookie { - return &PBSCookie{ +func newSampleCookie() *Cookie { + return &Cookie{ uids: map[string]uidWithExpiry{ "adnxs": newTempId("123", 10), "rubicon": newTempId("456", 10), @@ -383,24 +464,7 @@ func newSampleCookie() *PBSCookie { } } -func newTestCookie() (*PBSCookie, int) { - var mediumSizeCookie *PBSCookie = &PBSCookie{ - uids: map[string]uidWithExpiry{ - "key1": newTempId("12345678901234567890123456789012345678901234567890", 7), - "key2": newTempId("abcdefghijklmnopqrstuvwxyz", 6), - "key3": newTempId("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 6), - "key4": newTempId("12345678901234567890123456789612345678901234567890", 5), - "key5": newTempId("aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ", 4), - "key6": newTempId("12345678901234567890123456789012345678901234567890", 3), - "key7": newTempId("abcdefghijklmnopqrstuvwxyz", 2), - }, - optOut: false, - birthday: timestamp(), - } - return mediumSizeCookie, len(mediumSizeCookie.uids) -} - -func writeThenRead(cookie *PBSCookie, maxCookieSize int) *PBSCookie { +func writeThenRead(cookie *Cookie, maxCookieSize int) *Cookie { w := httptest.NewRecorder() hostCookie := &config.HostCookie{Domain: "mock-domain", MaxCookieSizeBytes: maxCookieSize} cookie.SetCookieOnResponse(w, false, hostCookie, 90*24*time.Hour) @@ -409,7 +473,7 @@ func writeThenRead(cookie *PBSCookie, maxCookieSize int) *PBSCookie { header := http.Header{} header.Add("Cookie", writtenCookie) request := http.Request{Header: header} - return ParsePBSCookieFromRequest(&request, hostCookie) + return ParseCookieFromRequest(&request, hostCookie) } func TestSetCookieOnResponseForSameSiteNone(t *testing.T) { @@ -422,9 +486,6 @@ func TestSetCookieOnResponseForSameSiteNone(t *testing.T) { cookie.SetCookieOnResponse(w, true, hostCookie, 90*24*time.Hour) writtenCookie := w.HeaderMap.Get("Set-Cookie") t.Log("Set-Cookie is: ", writtenCookie) - if !strings.Contains(writtenCookie, "SSCookie=1") { - t.Error("Set-Cookie should contain SSCookie=1") - } if !strings.Contains(writtenCookie, "; Secure;") { t.Error("Set-Cookie should contain Secure") } diff --git a/usersync/shuffler.go b/usersync/shuffler.go new file mode 100644 index 00000000000..a5bdcaab5ad --- /dev/null +++ b/usersync/shuffler.go @@ -0,0 +1,15 @@ +package usersync + +import "math/rand" + +// shuffler changes the order of elements in the slice. +type shuffler interface { + shuffle(v []string) +} + +// randomShuffler randomly changes the order of elements in the slice. +type randomShuffler struct{} + +func (randomShuffler) shuffle(v []string) { + rand.Shuffle(len(v), func(i, j int) { v[i], v[j] = v[j], v[i] }) +} diff --git a/usersync/shuffler_test.go b/usersync/shuffler_test.go new file mode 100644 index 00000000000..ab5ec8efd01 --- /dev/null +++ b/usersync/shuffler_test.go @@ -0,0 +1,42 @@ +package usersync + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestShuffler(t *testing.T) { + testCases := []struct { + description string + given []string + }{ + { + description: "Nil", + given: nil, + }, + { + description: "Empty", + given: []string{}, + }, + { + description: "One", + given: []string{"a"}, + }, + { + // at least 3 elements are required to test the swap logic. + description: "Many", + given: []string{"a", "b", "c"}, + }, + } + + for _, test := range testCases { + givenCopy := make([]string, len(test.given)) + copy(givenCopy, test.given) + + randomShuffler{}.shuffle(test.given) + + // ignores order of elements. we're testing the swap logic, not the rand shuffle algorithm. + assert.ElementsMatch(t, givenCopy, test.given, test.description) + } +} diff --git a/usersync/syncer.go b/usersync/syncer.go new file mode 100644 index 00000000000..2db220a2340 --- /dev/null +++ b/usersync/syncer.go @@ -0,0 +1,291 @@ +package usersync + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strings" + "text/template" + + validator "github.com/asaskevich/govalidator" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/privacy" +) + +var ( + errNoSyncTypesProvided = errors.New("no sync types provided") + errNoSyncTypesSupported = errors.New("no sync types supported") + errDefaultTypeMissingIFrame = errors.New("default is set to iframe but no iframe endpoint is configured") + errDefaultTypeMissingRedirect = errors.New("default is set to redirect but no redirect endpoint is configured") +) + +// Syncer represents the user sync configuration for a bidder or a shared set of bidders. +type Syncer interface { + // Key is the name of the syncer as stored in the user's cookie. This is often, but not + // necessarily, a one-to-one mapping with a bidder. + Key() string + + // DefaultSyncType is the default SyncType for this syncer. + DefaultSyncType() SyncType + + // SupportsType returns true if the syncer supports at least one of the specified sync types. + SupportsType(syncTypes []SyncType) bool + + // GetSync returns a user sync for the user's device to perform, or an error if the none of the + // sync types are supported or if macro substitution fails. + GetSync(syncTypes []SyncType, privacyPolicies privacy.Policies) (Sync, error) +} + +// Sync represents a user sync to be performed by the user's device. +type Sync struct { + URL string + Type SyncType + SupportCORS bool +} + +type standardSyncer struct { + key string + defaultSyncType SyncType + iframe *template.Template + redirect *template.Template + supportCORS bool +} + +const ( + setuidSyncTypeIFrame = "b" // b = blank HTML response + setuidSyncTypeRedirect = "i" // i = image response +) + +var ErrSyncerEndpointRequired = errors.New("at least one endpoint (iframe and/or redirect) is required") +var ErrSyncerKeyRequired = errors.New("key is required") +var ErrSyncerDefaultSyncTypeRequired = errors.New("default sync type is required when more then one sync endpoint is configured") + +// NewSyncer creates a new Syncer from the provided configuration, or an error if macro substition +// fails or an endpoint url is invalid. +func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer) (Syncer, error) { + if syncerConfig.Key == "" { + return nil, ErrSyncerKeyRequired + } + + if syncerConfig.IFrame == nil && syncerConfig.Redirect == nil { + return nil, ErrSyncerEndpointRequired + } + + syncer := standardSyncer{ + key: syncerConfig.Key, + supportCORS: syncerConfig.SupportCORS != nil && *syncerConfig.SupportCORS, + } + + if defaultSyncType, err := resolveDefaultSyncType(syncerConfig); err != nil { + return nil, err + } else { + syncer.defaultSyncType = defaultSyncType + } + + if syncerConfig.IFrame != nil { + var err error + syncer.iframe, err = buildTemplate(syncerConfig.Key, setuidSyncTypeIFrame, hostConfig, *syncerConfig.IFrame) + if err != nil { + return nil, fmt.Errorf("iframe %v", err) + } + if err := validateTemplate(syncer.iframe); err != nil { + return nil, fmt.Errorf("iframe %v", err) + } + } + + if syncerConfig.Redirect != nil { + var err error + syncer.redirect, err = buildTemplate(syncerConfig.Key, setuidSyncTypeRedirect, hostConfig, *syncerConfig.Redirect) + if err != nil { + return nil, fmt.Errorf("redirect %v", err) + } + if err := validateTemplate(syncer.redirect); err != nil { + return nil, fmt.Errorf("redirect %v", err) + } + } + + return syncer, nil +} + +func resolveDefaultSyncType(syncerConfig config.Syncer) (SyncType, error) { + if syncerConfig.Default == "" { + if syncerConfig.IFrame != nil && syncerConfig.Redirect != nil { + return SyncTypeUnknown, ErrSyncerDefaultSyncTypeRequired + } else if syncerConfig.IFrame != nil { + return SyncTypeIFrame, nil + } else { + return SyncTypeRedirect, nil + } + } + + if syncType, err := SyncTypeParse(syncerConfig.Default); err == nil { + switch syncType { + case SyncTypeIFrame: + if syncerConfig.IFrame == nil { + return SyncTypeUnknown, errDefaultTypeMissingIFrame + } + case SyncTypeRedirect: + if syncerConfig.Redirect == nil { + return SyncTypeUnknown, errDefaultTypeMissingRedirect + } + } + return syncType, nil + } + + return SyncTypeUnknown, fmt.Errorf("invalid default sync type '%s'", syncerConfig.Default) +} + +// macro substitution regex +var ( + macroRegexExternalHost = regexp.MustCompile(`{{\s*\.ExternalURL\s*}}`) + macroRegexSyncerKey = regexp.MustCompile(`{{\s*\.SyncerKey\s*}}`) + macroRegexSyncType = regexp.MustCompile(`{{\s*\.SyncType\s*}}`) + macroRegexUserMacro = regexp.MustCompile(`{{\s*\.UserMacro\s*}}`) + macroRegexRedirect = regexp.MustCompile(`{{\s*\.RedirectURL\s*}}`) + macroRegex = regexp.MustCompile(`{{\s*\..*?\s*}}`) +) + +func buildTemplate(key, syncTypeValue string, hostConfig config.UserSync, syncerEndpoint config.SyncerEndpoint) (*template.Template, error) { + redirectTemplate := syncerEndpoint.RedirectURL + if redirectTemplate == "" { + redirectTemplate = hostConfig.RedirectURL + } + + externalURL := syncerEndpoint.ExternalURL + if externalURL == "" { + externalURL = hostConfig.ExternalURL + } + + redirectURL := macroRegexSyncerKey.ReplaceAllLiteralString(redirectTemplate, key) + redirectURL = macroRegexSyncType.ReplaceAllLiteralString(redirectURL, syncTypeValue) + redirectURL = macroRegexUserMacro.ReplaceAllLiteralString(redirectURL, syncerEndpoint.UserMacro) + redirectURL = macroRegexExternalHost.ReplaceAllLiteralString(redirectURL, externalURL) + redirectURL = escapeTemplate(redirectURL) + + url := macroRegexRedirect.ReplaceAllString(syncerEndpoint.URL, redirectURL) + + templateName := strings.ToLower(key) + "_usersync_url" + return template.New(templateName).Parse(url) +} + +// escapeTemplate url encodes a string template leaving the macro tags unaffected. +func escapeTemplate(x string) string { + escaped := strings.Builder{} + + i := 0 + for _, m := range macroRegex.FindAllStringIndex(x, -1) { + escaped.WriteString(url.QueryEscape(x[i:m[0]])) + escaped.WriteString(x[m[0]:m[1]]) + i = m[1] + } + escaped.WriteString(url.QueryEscape(x[i:])) + + return escaped.String() +} + +var templateTestValues = macros.UserSyncTemplateParams{ + GDPR: "anyGDPR", + GDPRConsent: "anyGDPRConsent", + USPrivacy: "anyCCPAConsent", +} + +func validateTemplate(template *template.Template) error { + url, err := macros.ResolveMacros(template, templateTestValues) + if err != nil { + return err + } + + if !validator.IsURL(url) || !validator.IsRequestURL(url) { + return fmt.Errorf(`composed url: "%s" is invalid`, url) + } + + return nil +} + +func (s standardSyncer) Key() string { + return s.key +} + +func (s standardSyncer) DefaultSyncType() SyncType { + return s.defaultSyncType +} + +func (s standardSyncer) SupportsType(syncTypes []SyncType) bool { + supported := s.filterSupportedSyncTypes(syncTypes) + return len(supported) > 0 +} + +func (s standardSyncer) filterSupportedSyncTypes(syncTypes []SyncType) []SyncType { + supported := make([]SyncType, 0, len(syncTypes)) + for _, syncType := range syncTypes { + switch syncType { + case SyncTypeIFrame: + if s.iframe != nil { + supported = append(supported, SyncTypeIFrame) + } + case SyncTypeRedirect: + if s.redirect != nil { + supported = append(supported, SyncTypeRedirect) + } + } + } + return supported +} + +func (s standardSyncer) GetSync(syncTypes []SyncType, privacyPolicies privacy.Policies) (Sync, error) { + syncType, err := s.chooseSyncType(syncTypes) + if err != nil { + return Sync{}, err + } + + syncTemplate := s.chooseTemplate(syncType) + + url, err := macros.ResolveMacros(syncTemplate, macros.UserSyncTemplateParams{ + GDPR: privacyPolicies.GDPR.Signal, + GDPRConsent: privacyPolicies.GDPR.Consent, + USPrivacy: privacyPolicies.CCPA.Consent, + }) + if err != nil { + return Sync{}, err + } + + sync := Sync{ + URL: url, + Type: syncType, + SupportCORS: s.supportCORS, + } + return sync, nil +} + +func (s standardSyncer) chooseSyncType(syncTypes []SyncType) (SyncType, error) { + if len(syncTypes) == 0 { + return SyncTypeUnknown, errNoSyncTypesProvided + } + + supported := s.filterSupportedSyncTypes(syncTypes) + if len(supported) == 0 { + return SyncTypeUnknown, errNoSyncTypesSupported + } + + // prefer default type + for _, syncType := range supported { + if syncType == s.defaultSyncType { + return syncType, nil + } + } + + return syncTypes[0], nil +} + +func (s standardSyncer) chooseTemplate(syncType SyncType) *template.Template { + switch syncType { + case SyncTypeIFrame: + return s.iframe + case SyncTypeRedirect: + return s.redirect + default: + return nil + } +} diff --git a/usersync/syncer_test.go b/usersync/syncer_test.go new file mode 100644 index 00000000000..2f4e3203905 --- /dev/null +++ b/usersync/syncer_test.go @@ -0,0 +1,754 @@ +package usersync + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/macros" + "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 TestNewSyncer(t *testing.T) { + var ( + supportCORS = true + hostConfig = config.UserSync{ExternalURL: "http://host.com", RedirectURL: "{{.ExternalURL}}/host"} + macroValues = macros.UserSyncTemplateParams{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"} + iframeConfig = &config.SyncerEndpoint{URL: "https://bidder.com/iframe?redirect={{.RedirectURL}}"} + redirectConfig = &config.SyncerEndpoint{URL: "https://bidder.com/redirect?redirect={{.RedirectURL}}"} + errParseConfig = &config.SyncerEndpoint{URL: "{{malformed}}"} + errInvalidConfig = &config.SyncerEndpoint{URL: "notAURL:{{.RedirectURL}}"} + ) + + testCases := []struct { + description string + givenKey string + givenDefault string + givenIFrameConfig *config.SyncerEndpoint + givenRedirectConfig *config.SyncerEndpoint + expectedError string + expectedDefault SyncType + expectedIFrame string + expectedRedirect string + }{ + { + description: "Missing Key", + givenKey: "", + givenDefault: "iframe", + givenIFrameConfig: iframeConfig, + givenRedirectConfig: nil, + expectedError: "key is required", + }, + { + description: "Missing Endpoints", + givenKey: "a", + givenDefault: "", + givenIFrameConfig: nil, + givenRedirectConfig: nil, + expectedError: "at least one endpoint (iframe and/or redirect) is required", + }, + { + description: "Resolve Default Sync Type Error ", + givenKey: "a", + givenDefault: "", + givenIFrameConfig: iframeConfig, + givenRedirectConfig: redirectConfig, + expectedError: "default sync type is required when more then one sync endpoint is configured", + }, + { + description: "IFrame & Redirect Endpoints", + givenKey: "a", + givenDefault: "iframe", + givenIFrameConfig: iframeConfig, + givenRedirectConfig: redirectConfig, + expectedDefault: SyncTypeIFrame, + expectedIFrame: "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fhost", + expectedRedirect: "https://bidder.com/redirect?redirect=http%3A%2F%2Fhost.com%2Fhost", + }, + { + description: "IFrame - Parse Error", + givenKey: "a", + givenDefault: "iframe", + givenIFrameConfig: errParseConfig, + givenRedirectConfig: nil, + expectedError: "iframe template: a_usersync_url:1: function \"malformed\" not defined", + }, + { + description: "IFrame - Validation Error", + givenKey: "a", + givenDefault: "iframe", + givenIFrameConfig: errInvalidConfig, + givenRedirectConfig: nil, + expectedError: "iframe composed url: \"notAURL:http%3A%2F%2Fhost.com%2Fhost\" is invalid", + }, + { + description: "Redirect - Parse Error", + givenKey: "a", + givenDefault: "redirect", + givenIFrameConfig: nil, + givenRedirectConfig: errParseConfig, + expectedError: "redirect template: a_usersync_url:1: function \"malformed\" not defined", + }, + { + description: "Redirect - Validation Error", + givenKey: "a", + givenDefault: "redirect", + givenIFrameConfig: nil, + givenRedirectConfig: errInvalidConfig, + expectedError: "redirect composed url: \"notAURL:http%3A%2F%2Fhost.com%2Fhost\" is invalid", + }, + } + + for _, test := range testCases { + syncerConfig := config.Syncer{ + Key: test.givenKey, + SupportCORS: &supportCORS, + Default: test.givenDefault, + IFrame: test.givenIFrameConfig, + Redirect: test.givenRedirectConfig, + } + + result, err := NewSyncer(hostConfig, syncerConfig) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + if assert.IsType(t, standardSyncer{}, result, test.description+":result_type") { + result := result.(standardSyncer) + assert.Equal(t, test.givenKey, result.key, test.description+":key") + assert.Equal(t, supportCORS, result.supportCORS, test.description+":cors") + assert.Equal(t, test.expectedDefault, result.defaultSyncType, test.description+":default_sync") + + if test.expectedIFrame == "" { + assert.Nil(t, result.iframe, test.description+":iframe") + } else { + iframeRendered, err := macros.ResolveMacros(result.iframe, macroValues) + if assert.NoError(t, err, test.description+":iframe_render") { + assert.Equal(t, test.expectedIFrame, iframeRendered, test.description+":iframe") + } + } + + if test.expectedRedirect == "" { + assert.Nil(t, result.redirect, test.description+":redirect") + } else { + redirectRendered, err := macros.ResolveMacros(result.redirect, macroValues) + if assert.NoError(t, err, test.description+":redirect_render") { + assert.Equal(t, test.expectedRedirect, redirectRendered, test.description+":redirect") + } + } + } + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + assert.Empty(t, result) + } + } +} + +func TestResolveDefaultSyncType(t *testing.T) { + anyEndpoint := &config.SyncerEndpoint{} + + testCases := []struct { + description string + givenConfig config.Syncer + expectedSyncType SyncType + expectedError string + }{ + { + description: "IFrame & Redirect - IFrame Default", + givenConfig: config.Syncer{Default: "iframe", IFrame: anyEndpoint, Redirect: anyEndpoint}, + expectedSyncType: SyncTypeIFrame, + }, + { + description: "IFrame & Redirect - Redirect Default", + givenConfig: config.Syncer{Default: "redirect", IFrame: anyEndpoint, Redirect: anyEndpoint}, + expectedSyncType: SyncTypeRedirect, + }, + { + description: "IFrame & Redirect - No Default", + givenConfig: config.Syncer{Default: "", IFrame: anyEndpoint, Redirect: anyEndpoint}, + expectedSyncType: SyncTypeUnknown, + expectedError: "default sync type is required when more then one sync endpoint is configured", + }, + { + description: "IFrame & Redirect - Invalid Default", + givenConfig: config.Syncer{Default: "invalid", IFrame: anyEndpoint, Redirect: anyEndpoint}, + expectedSyncType: SyncTypeUnknown, + expectedError: "invalid default sync type 'invalid'", + }, + { + description: "IFrame Only - IFrame Default", + givenConfig: config.Syncer{Default: "iframe", IFrame: anyEndpoint, Redirect: nil}, + expectedSyncType: SyncTypeIFrame, + }, + { + description: "IFrame Only - No Default", + givenConfig: config.Syncer{Default: "", IFrame: anyEndpoint, Redirect: nil}, + expectedSyncType: SyncTypeIFrame, + }, + { + description: "IFrame Only - Redirect Default", + givenConfig: config.Syncer{Default: "redirect", IFrame: anyEndpoint, Redirect: nil}, + expectedSyncType: SyncTypeUnknown, + expectedError: "default is set to redirect but no redirect endpoint is configured", + }, + { + description: "Redirect Only - Redirect Default", + givenConfig: config.Syncer{Default: "redirect", IFrame: nil, Redirect: anyEndpoint}, + expectedSyncType: SyncTypeRedirect, + }, + { + description: "IFrame Only - No Default", + givenConfig: config.Syncer{Default: "", IFrame: nil, Redirect: anyEndpoint}, + expectedSyncType: SyncTypeRedirect, + }, + { + description: "IFrame Only - IFrame Default", + givenConfig: config.Syncer{Default: "iframe", IFrame: nil, Redirect: anyEndpoint}, + expectedSyncType: SyncTypeUnknown, + expectedError: "default is set to iframe but no iframe endpoint is configured", + }, + } + + for _, test := range testCases { + result, err := resolveDefaultSyncType(test.givenConfig) + + assert.Equal(t, test.expectedSyncType, result, test.description+":result") + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + } +} + +func TestBuildTemplate(t *testing.T) { + var ( + key = "anyKey" + syncTypeValue = "x" + hostConfig = config.UserSync{ExternalURL: "http://host.com", RedirectURL: "{{.ExternalURL}}/host"} + macroValues = macros.UserSyncTemplateParams{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"} + ) + + testCases := []struct { + description string + givenHostConfig config.UserSync + givenSyncerEndpoint config.SyncerEndpoint + expectedError string + expectedRendered string + }{ + { + description: "No Composed Macros", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "hasNoComposedMacros,gdpr={{.GDPR}}", + }, + expectedRendered: "hasNoComposedMacros,gdpr=A", + }, + { + description: "All Composed Macros", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + RedirectURL: "{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&f={{.SyncType}}&gdpr={{.GDPR}}&uid={{.UserMacro}}", + ExternalURL: "http://syncer.com", + UserMacro: "$UID$", + }, + expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fsetuid%3Fbidder%3DanyKey%26f%3Dx%26gdpr%3DA%26uid%3D%24UID%24", + }, + { + description: "Redirect URL + External URL From Host", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + }, + expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fhost.com%2Fhost", + }, + { + description: "Redirect URL From Syncer", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + RedirectURL: "{{.ExternalURL}}/syncer", + }, + expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fhost.com%2Fsyncer", + }, + { + description: "External URL From Host", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + ExternalURL: "http://syncer.com", + }, + expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fhost", + }, + { + description: "Template Parse Error", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "{{malformed}}", + }, + expectedError: "template: anykey_usersync_url:1: function \"malformed\" not defined", + }, + { + description: "User Macro Is Go Template Macro-Like", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + RedirectURL: "{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&f={{.SyncType}}&gdpr={{.GDPR}}&uid={{.UserMacro}}", + UserMacro: "{{UID}}", + }, + expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fhost.com%2Fsetuid%3Fbidder%3DanyKey%26f%3Dx%26gdpr%3DA%26uid%3D%7B%7BUID%7D%7D", + }, + + // The following tests protect against the "\"." literal character vs the "." character class in regex. Literal + // value which use {{ }} but do not match Go's naming pattern of {{ .Name }} are escaped. + { + description: "Invalid Macro - Redirect URL", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{xRedirectURL}}", + }, + expectedError: "template: anykey_usersync_url:1: function \"xRedirectURL\" not defined", + }, + { + description: "Macro-Like Literal Value - External URL", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + RedirectURL: "{{xExternalURL}}", + }, + expectedRendered: "https://bidder.com/sync?redirect=%7B%7BxExternalURL%7D%7D", + }, + { + description: "Macro-Like Literal Value - Syncer Key", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + RedirectURL: "{{xSyncerKey}}", + }, + expectedRendered: "https://bidder.com/sync?redirect=%7B%7BxSyncerKey%7D%7D", + }, + { + description: "Macro-Like Literal Value - Sync Type", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + RedirectURL: "{{xSyncType}}", + }, + expectedRendered: "https://bidder.com/sync?redirect=%7B%7BxSyncType%7D%7D", + }, + { + description: "Macro-Like Literal Value - User Macro", + givenSyncerEndpoint: config.SyncerEndpoint{ + URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", + RedirectURL: "{{xUserMacro}}", + }, + expectedRendered: "https://bidder.com/sync?redirect=%7B%7BxUserMacro%7D%7D", + }, + } + + for _, test := range testCases { + result, err := buildTemplate(key, syncTypeValue, hostConfig, test.givenSyncerEndpoint) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + resultRendered, err := macros.ResolveMacros(result, macroValues) + if assert.NoError(t, err, test.description+":template_render") { + assert.Equal(t, test.expectedRendered, resultRendered, test.description+":template") + } + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + } +} + +func TestEscapeTemplate(t *testing.T) { + testCases := []struct { + description string + given string + expected string + }{ + { + description: "Macro Only", + given: "{{.Macro}}", + expected: "{{.Macro}}", + }, + { + description: "Text Only", + given: "/a", + expected: "%2Fa", + }, + { + description: "Start Only", + given: "&a{{.Macro1}}", + expected: "%26a{{.Macro1}}", + }, + { + description: "Middle Only", + given: "{{.Macro1}}&a{{.Macro2}}", + expected: "{{.Macro1}}%26a{{.Macro2}}", + }, + { + description: "End Only", + given: "{{.Macro1}}&a", + expected: "{{.Macro1}}%26a", + }, + { + description: "Start / Middle / End", + given: "&a{{.Macro1}}/b{{.Macro2}}&c", + expected: "%26a{{.Macro1}}%2Fb{{.Macro2}}%26c", + }, + { + description: "Characters In Macro Not Escaped", + given: "{{.Macro&}}", + expected: "{{.Macro&}}", + }, + { + description: "Macro Whitespace Insensitive", + given: " &a {{ .Macro1 }} /b ", + expected: "+%26a+{{ .Macro1 }}+%2Fb+", + }, + { + description: "Double Curly Braces, But Not Macro", + given: "{{Macro}}", + expected: "%7B%7BMacro%7D%7D", + }, + } + + for _, test := range testCases { + result := escapeTemplate(test.given) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestValidateTemplate(t *testing.T) { + testCases := []struct { + description string + given *template.Template + expectedError string + }{ + { + description: "Contains Unrecognized Macro", + given: template.Must(template.New("test").Parse("invalid:{{.DoesNotExist}}")), + expectedError: "template: test:1:10: executing \"test\" at <.DoesNotExist>: can't evaluate field DoesNotExist in type macros.UserSyncTemplateParams", + }, + { + description: "Not A Url", + given: template.Must(template.New("test").Parse("not-a-url,gdpr:{{.GDPR}},gdprconsent:{{.GDPRConsent}},ccpa:{{.USPrivacy}}")), + expectedError: "composed url: \"not-a-url,gdpr:anyGDPR,gdprconsent:anyGDPRConsent,ccpa:anyCCPAConsent\" is invalid", + }, + { + description: "Valid", + given: template.Must(template.New("test").Parse("http://server.com/sync?gdpr={{.GDPR}}&gdprconsent={{.GDPRConsent}}&ccpa={{.USPrivacy}}")), + expectedError: "", + }, + } + + for _, test := range testCases { + err := validateTemplate(test.given) + + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } +} + +func TestSyncerKey(t *testing.T) { + syncer := standardSyncer{key: "a"} + assert.Equal(t, "a", syncer.Key()) +} + +func TestSyncerDefaultSyncType(t *testing.T) { + syncer := standardSyncer{defaultSyncType: SyncTypeRedirect} + assert.Equal(t, SyncTypeRedirect, syncer.DefaultSyncType()) +} + +func TestSyncerSupportsType(t *testing.T) { + endpointTemplate := template.Must(template.New("test").Parse("iframe")) + + testCases := []struct { + description string + givenSyncTypes []SyncType + givenIFrameTemplate *template.Template + givenRedirectTemplate *template.Template + expectedResult bool + }{ + { + description: "All Available - None", + givenSyncTypes: []SyncType{}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: endpointTemplate, + expectedResult: false, + }, + { + description: "All Available - One", + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: endpointTemplate, + expectedResult: true, + }, + { + description: "All Available - Many", + givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: endpointTemplate, + expectedResult: true, + }, + { + description: "One Available - None", + givenSyncTypes: []SyncType{}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: nil, + expectedResult: false, + }, + { + description: "One Available - One - Supported", + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: nil, + expectedResult: true, + }, + { + description: "One Available - One - Not Supported", + givenSyncTypes: []SyncType{SyncTypeRedirect}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: nil, + expectedResult: false, + }, + { + description: "One Available - Many", + givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: nil, + expectedResult: true, + }, + } + + for _, test := range testCases { + syncer := standardSyncer{ + iframe: test.givenIFrameTemplate, + redirect: test.givenRedirectTemplate, + } + result := syncer.SupportsType(test.givenSyncTypes) + assert.Equal(t, test.expectedResult, result, test.description) + } +} + +func TestSyncerFilterSupportedSyncTypes(t *testing.T) { + endpointTemplate := template.Must(template.New("test").Parse("iframe")) + + testCases := []struct { + description string + givenSyncTypes []SyncType + givenIFrameTemplate *template.Template + givenRedirectTemplate *template.Template + expectedSyncTypes []SyncType + }{ + { + description: "All Available - None", + givenSyncTypes: []SyncType{}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: endpointTemplate, + expectedSyncTypes: []SyncType{}, + }, + { + description: "All Available - One", + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: endpointTemplate, + expectedSyncTypes: []SyncType{SyncTypeIFrame}, + }, + { + description: "All Available - Many", + givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: endpointTemplate, + expectedSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, + }, + { + description: "One Available - None", + givenSyncTypes: []SyncType{}, + givenIFrameTemplate: nil, + givenRedirectTemplate: endpointTemplate, + expectedSyncTypes: []SyncType{}, + }, + { + description: "One Available - One - Not Supported", + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenIFrameTemplate: nil, + givenRedirectTemplate: endpointTemplate, + expectedSyncTypes: []SyncType{}, + }, + { + description: "One Available - One - Supported", + givenSyncTypes: []SyncType{SyncTypeRedirect}, + givenIFrameTemplate: nil, + givenRedirectTemplate: endpointTemplate, + expectedSyncTypes: []SyncType{SyncTypeRedirect}, + }, + { + description: "One Available - Many", + givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, + givenIFrameTemplate: nil, + givenRedirectTemplate: endpointTemplate, + expectedSyncTypes: []SyncType{SyncTypeRedirect}, + }, + } + + for _, test := range testCases { + syncer := standardSyncer{ + iframe: test.givenIFrameTemplate, + redirect: test.givenRedirectTemplate, + } + result := syncer.filterSupportedSyncTypes(test.givenSyncTypes) + assert.ElementsMatch(t, test.expectedSyncTypes, result, test.description) + } +} + +func TestSyncerGetSync(t *testing.T) { + var ( + iframeTemplate = template.Must(template.New("test").Parse("iframe,gdpr:{{.GDPR}},gdprconsent:{{.GDPRConsent}},ccpa:{{.USPrivacy}}")) + redirectTemplate = template.Must(template.New("test").Parse("redirect,gdpr:{{.GDPR}},gdprconsent:{{.GDPRConsent}},ccpa:{{.USPrivacy}}")) + malformedTemplate = template.Must(template.New("test").Parse("malformed,invalid:{{.DoesNotExist}}")) + ) + + testCases := []struct { + description string + givenSyncer standardSyncer + givenSyncTypes []SyncType + givenPrivacyPolicies privacy.Policies + expectedError string + expectedSync Sync + }{ + { + description: "No Sync Types", + givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, + givenSyncTypes: []SyncType{}, + givenPrivacyPolicies: privacy.Policies{GDPR: gdpr.Policy{Signal: "A", Consent: "B"}, CCPA: ccpa.Policy{Consent: "C"}}, + expectedError: "no sync types provided", + }, + { + description: "IFrame", + givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenPrivacyPolicies: privacy.Policies{GDPR: gdpr.Policy{Signal: "A", Consent: "B"}, CCPA: ccpa.Policy{Consent: "C"}}, + expectedSync: Sync{URL: "iframe,gdpr:A,gdprconsent:B,ccpa:C", Type: SyncTypeIFrame, SupportCORS: false}, + }, + { + description: "Redirect", + givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, + givenSyncTypes: []SyncType{SyncTypeRedirect}, + givenPrivacyPolicies: privacy.Policies{GDPR: gdpr.Policy{Signal: "A", Consent: "B"}, CCPA: ccpa.Policy{Consent: "C"}}, + expectedSync: Sync{URL: "redirect,gdpr:A,gdprconsent:B,ccpa:C", Type: SyncTypeRedirect, SupportCORS: false}, + }, + { + description: "Macro Error", + givenSyncer: standardSyncer{iframe: malformedTemplate}, + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenPrivacyPolicies: privacy.Policies{GDPR: gdpr.Policy{Signal: "A", Consent: "B"}, CCPA: ccpa.Policy{Consent: "C"}}, + expectedError: "template: test:1:20: executing \"test\" at <.DoesNotExist>: can't evaluate field DoesNotExist in type macros.UserSyncTemplateParams", + }, + } + + for _, test := range testCases { + result, err := test.givenSyncer.GetSync(test.givenSyncTypes, test.givenPrivacyPolicies) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + assert.Equal(t, test.expectedSync, result, test.description+":sync") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + } +} + +func TestSyncerChooseSyncType(t *testing.T) { + endpointTemplate := template.Must(template.New("test").Parse("iframe")) + + testCases := []struct { + description string + givenSyncTypes []SyncType + givenDefaultSyncType SyncType + givenIFrameTemplate *template.Template + givenRedirectTemplate *template.Template + expectedError string + expectedSyncType SyncType + }{ + { + description: "None Available - Error", + givenSyncTypes: []SyncType{}, + givenDefaultSyncType: SyncTypeRedirect, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: endpointTemplate, + expectedError: "no sync types provided", + }, + { + description: "All Available - Choose Default", + givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, + givenDefaultSyncType: SyncTypeRedirect, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: endpointTemplate, + expectedSyncType: SyncTypeRedirect, + }, + { + description: "Default Not Available - Choose Other One", + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenDefaultSyncType: SyncTypeRedirect, + givenIFrameTemplate: endpointTemplate, + givenRedirectTemplate: endpointTemplate, + expectedSyncType: SyncTypeIFrame, + }, + { + description: "None Supported - Error", + givenSyncTypes: []SyncType{SyncTypeIFrame}, + givenDefaultSyncType: SyncTypeRedirect, + givenIFrameTemplate: nil, + givenRedirectTemplate: endpointTemplate, + expectedError: "no sync types supported", + }, + } + + for _, test := range testCases { + syncer := standardSyncer{ + defaultSyncType: test.givenDefaultSyncType, + iframe: test.givenIFrameTemplate, + redirect: test.givenRedirectTemplate, + } + result, err := syncer.chooseSyncType(test.givenSyncTypes) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + assert.Equal(t, test.expectedSyncType, result, test.description+":sync_type") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + } +} + +func TestSyncerChooseTemplate(t *testing.T) { + var ( + iframeTemplate = template.Must(template.New("test").Parse("iframe")) + redirectTemplate = template.Must(template.New("test").Parse("redirect")) + ) + + testCases := []struct { + description string + givenSyncType SyncType + expectedTemplate *template.Template + }{ + { + description: "IFrame", + givenSyncType: SyncTypeIFrame, + expectedTemplate: iframeTemplate, + }, + { + description: "Redirect", + givenSyncType: SyncTypeRedirect, + expectedTemplate: redirectTemplate, + }, + { + description: "Invalid", + givenSyncType: SyncType("invalid"), + expectedTemplate: nil, + }, + } + + for _, test := range testCases { + syncer := standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate} + result := syncer.chooseTemplate(test.givenSyncType) + assert.Equal(t, test.expectedTemplate, result, test.description) + } +} diff --git a/usersync/syncersbuilder.go b/usersync/syncersbuilder.go new file mode 100644 index 00000000000..95b3e98962c --- /dev/null +++ b/usersync/syncersbuilder.go @@ -0,0 +1,126 @@ +package usersync + +import ( + "fmt" + "sort" + "strings" + + "github.com/prebid/prebid-server/config" +) + +type namedSyncerConfig struct { + name string + cfg config.Syncer +} + +// SyncerBuildError represents an error with building a syncer. +type SyncerBuildError struct { + Bidder string + SyncerKey string + Err error +} + +// Error implements the standard error interface. +func (e SyncerBuildError) Error() string { + return fmt.Sprintf("cannot create syncer for bidder %s with key %s: %v", e.Bidder, e.SyncerKey, e.Err) +} + +func BuildSyncers(hostConfig *config.Configuration, bidderInfos config.BidderInfos) (map[string]Syncer, []error) { + // map syncer config by bidder + cfgByBidder := make(map[string]config.Syncer, len(bidderInfos)) + for bidder, cfg := range bidderInfos { + if shouldCreateSyncer(cfg) { + cfgByBidder[bidder] = *cfg.Syncer + } + } + + // map syncer config by key + cfgBySyncerKey := make(map[string][]namedSyncerConfig, len(bidderInfos)) + for bidder, cfg := range cfgByBidder { + if cfg.Key == "" { + cfg.Key = bidder + } + cfgBySyncerKey[cfg.Key] = append(cfgBySyncerKey[cfg.Key], namedSyncerConfig{bidder, cfg}) + } + + // resolve host endpoint + hostUserSyncConfig := hostConfig.UserSync + if hostUserSyncConfig.ExternalURL == "" { + hostUserSyncConfig.ExternalURL = hostConfig.ExternalURL + } + + // create syncers + errs := []error{} + syncers := make(map[string]Syncer, len(bidderInfos)) + for key, cfgGroup := range cfgBySyncerKey { + primaryCfg, err := chooseSyncerConfig(cfgGroup) + if err != nil { + errs = append(errs, err) + continue + } + + syncer, err := NewSyncer(hostUserSyncConfig, primaryCfg.cfg) + if err != nil { + errs = append(errs, SyncerBuildError{ + Bidder: primaryCfg.name, + SyncerKey: key, + Err: err, + }) + continue + } + + for _, bidder := range cfgGroup { + syncers[bidder.name] = syncer + } + } + + if len(errs) > 0 { + return nil, errs + } + return syncers, nil +} + +func shouldCreateSyncer(cfg config.BidderInfo) bool { + if !cfg.Enabled { + return false + } + + if cfg.Syncer == nil { + return false + } + + // a syncer may provide just a Supports field to provide hints to the host. we should only try to create a syncer + // if there is at least one non-Supports value populated. + return cfg.Syncer.Key != "" || cfg.Syncer.Default != "" || cfg.Syncer.IFrame != nil || cfg.Syncer.Redirect != nil || cfg.Syncer.SupportCORS != nil +} + +func chooseSyncerConfig(biddersSyncerConfig []namedSyncerConfig) (namedSyncerConfig, error) { + if len(biddersSyncerConfig) == 1 { + return biddersSyncerConfig[0], nil + } + + var bidderNames []string + var bidderNamesWithEndpoints []string + var syncerConfig namedSyncerConfig + for _, bidder := range biddersSyncerConfig { + bidderNames = append(bidderNames, bidder.name) + if bidder.cfg.IFrame != nil || bidder.cfg.Redirect != nil { + bidderNamesWithEndpoints = append(bidderNamesWithEndpoints, bidder.name) + syncerConfig = bidder + } + } + + if len(bidderNamesWithEndpoints) == 0 { + sort.Strings(bidderNames) + bidders := strings.Join(bidderNames, ", ") + return namedSyncerConfig{}, fmt.Errorf("bidders %s share the same syncer key, but none define endpoints (iframe and/or redirect)", bidders) + } + + if len(bidderNamesWithEndpoints) > 1 { + sort.Strings(bidderNamesWithEndpoints) + bidders := strings.Join(bidderNamesWithEndpoints, ", ") + return namedSyncerConfig{}, fmt.Errorf("bidders %s define endpoints (iframe and/or redirect) for the same syncer key, but only one bidder is permitted to define endpoints", bidders) + } + + return syncerConfig, nil +} diff --git a/usersync/syncersbuilder_test.go b/usersync/syncersbuilder_test.go new file mode 100644 index 00000000000..e36b1c2ee52 --- /dev/null +++ b/usersync/syncersbuilder_test.go @@ -0,0 +1,340 @@ +package usersync + +import ( + "errors" + "testing" + + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestSyncerBuildError(t *testing.T) { + err := SyncerBuildError{ + Bidder: "anyBidder", + SyncerKey: "anyKey", + Err: errors.New("anyError"), + } + assert.Equal(t, err.Error(), "cannot create syncer for bidder anyBidder with key anyKey: anyError") +} + +func TestBuildSyncers(t *testing.T) { + var ( + hostConfig = config.Configuration{ExternalURL: "http://host.com", UserSync: config.UserSync{RedirectURL: "{{.ExternalURL}}/{{.SyncerKey}}/host"}} + iframeConfig = &config.SyncerEndpoint{URL: "https://bidder.com/iframe?redirect={{.RedirectURL}}"} + infoKeyAPopulated = config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "a", IFrame: iframeConfig}} + infoKeyADisabled = config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Key: "a", IFrame: iframeConfig}} + infoKeyAEmpty = config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "a"}} + infoKeyAError = config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "a", Default: "redirect", IFrame: iframeConfig}} // Error caused by invalid default sync type + infoKeyASupportsOnly = config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Supports: []string{"iframe"}}} + infoKeyBPopulated = config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "b", IFrame: iframeConfig}} + infoKeyBEmpty = config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "b"}} + infoKeyMissingPopulated = config.BidderInfo{Enabled: true, Syncer: &config.Syncer{IFrame: iframeConfig}} + ) + + // NOTE: The hostConfig includes the syncer key in the RedirectURL to distinguish between the syncer keys + // in these tests. Look carefully at the end of the expected iframe urls to see the syncer key. + + testCases := []struct { + description string + givenConfig config.Configuration + givenBidderInfos config.BidderInfos + expectedIFramesURLs map[string]string + expectedErrors []string + }{ + { + description: "One", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated}, + expectedIFramesURLs: map[string]string{ + "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fa%2Fhost", + }, + }, + { + description: "One - Missing Key - Defaults To Bidder Name", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyMissingPopulated}, + expectedIFramesURLs: map[string]string{ + "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fbidder1%2Fhost", + }, + }, + { + description: "One - Syncer Error", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAError}, + expectedErrors: []string{ + "cannot create syncer for bidder bidder1 with key a: default is set to redirect but no redirect endpoint is configured", + }, + }, + { + description: "Many - Different Syncers", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated, "bidder2": infoKeyBPopulated}, + expectedIFramesURLs: map[string]string{ + "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fa%2Fhost", + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fb%2Fhost", + }, + }, + { + description: "Many - Same Syncers - One Primary", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated, "bidder2": infoKeyAEmpty}, + expectedIFramesURLs: map[string]string{ + "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fa%2Fhost", + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fa%2Fhost", + }, + }, + { + description: "Many - Same Syncers - Many Primaries", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated, "bidder2": infoKeyAPopulated}, + expectedErrors: []string{ + "bidders bidder1, bidder2 define endpoints (iframe and/or redirect) for the same syncer key, but only one bidder is permitted to define endpoints", + }, + }, + { + description: "Many - Same Syncers - Many Primaries - None Populated", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAEmpty, "bidder2": infoKeyAEmpty}, + expectedErrors: []string{ + "bidders bidder1, bidder2 share the same syncer key, but none define endpoints (iframe and/or redirect)", + }, + }, + { + description: "Many - Sync Error - Bidder Correct", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAEmpty, "bidder2": infoKeyAError}, + expectedErrors: []string{ + "cannot create syncer for bidder bidder2 with key a: default is set to redirect but no redirect endpoint is configured", + }, + }, + { + description: "Many - Empty Syncers Ignored", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": {}, "bidder2": infoKeyBPopulated}, + expectedIFramesURLs: map[string]string{ + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fb%2Fhost", + }, + }, + { + description: "Many - Disabled Syncers Ignored", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyADisabled, "bidder2": infoKeyBPopulated}, + expectedIFramesURLs: map[string]string{ + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fb%2Fhost", + }, + }, + { + description: "Many - Supports Only Syncers Ignored", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyASupportsOnly, "bidder2": infoKeyBPopulated}, + expectedIFramesURLs: map[string]string{ + "bidder2": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fb%2Fhost", + }, + }, + { + description: "Many - Multiple Errors", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAError, "bidder2": infoKeyBEmpty}, + expectedErrors: []string{ + "cannot create syncer for bidder bidder1 with key a: default is set to redirect but no redirect endpoint is configured", + "cannot create syncer for bidder bidder2 with key b: at least one endpoint (iframe and/or redirect) is required", + }, + }, + { + description: "ExternalURL Host User Sync Override", + givenConfig: config.Configuration{ExternalURL: "http://host.com", UserSync: config.UserSync{ExternalURL: "http://hostoverride.com", RedirectURL: "{{.ExternalURL}}/{{.SyncerKey}}/host"}}, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated}, + expectedIFramesURLs: map[string]string{ + "bidder1": "https://bidder.com/iframe?redirect=http%3A%2F%2Fhostoverride.com%2Fa%2Fhost", + }, + }, + } + + for _, test := range testCases { + result, errs := BuildSyncers(&test.givenConfig, test.givenBidderInfos) + + if len(test.expectedErrors) == 0 { + assert.Empty(t, errs, test.description+":err") + resultRenderedIFrameURLS := map[string]string{} + for k, v := range result { + iframeRendered, err := v.GetSync([]SyncType{SyncTypeIFrame}, privacy.Policies{}) + if assert.NoError(t, err, test.description+"key:%s,:iframe_render", k) { + resultRenderedIFrameURLS[k] = iframeRendered.URL + } + } + assert.Equal(t, test.expectedIFramesURLs, resultRenderedIFrameURLS, test.description+":result") + } else { + errMessages := make([]string, 0, len(errs)) + for _, e := range errs { + errMessages = append(errMessages, e.Error()) + } + assert.ElementsMatch(t, test.expectedErrors, errMessages, test.description+":err") + assert.Empty(t, result, test.description+":result") + } + } +} + +func TestShouldCreateSyncer(t *testing.T) { + var ( + anySupports = []string{"iframe"} + anyEndpoint = &config.SyncerEndpoint{} + anyCORS = true + ) + + testCases := []struct { + description string + given config.BidderInfo + expected bool + }{ + { + description: "Enabled, No Syncer", + given: config.BidderInfo{Enabled: true, Syncer: nil}, + expected: false, + }, + { + description: "Enabled, Syncer", + given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "anyKey"}}, + expected: true, + }, + { + description: "Enabled, Syncer - Fully Loaded", + given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "anyKey", Default: "iframe", Supports: anySupports, IFrame: anyEndpoint, Redirect: anyEndpoint, SupportCORS: &anyCORS}}, + expected: true, + }, + { + description: "Enabled, Syncer - Only Key", + given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Key: "anyKey"}}, + expected: true, + }, + { + description: "Enabled, Syncer - Only Default", + given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Default: "iframe"}}, + expected: true, + }, + { + description: "Enabled, Syncer - Only Supports", + given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Supports: anySupports}}, + expected: false, + }, + { + description: "Enabled, Syncer - Only IFrame", + given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{IFrame: anyEndpoint}}, + expected: true, + }, + { + description: "Enabled, Syncer - Only Redirect", + given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{Redirect: anyEndpoint}}, + expected: true, + }, + { + description: "Enabled, Syncer - Only SupportCORS", + given: config.BidderInfo{Enabled: true, Syncer: &config.Syncer{SupportCORS: &anyCORS}}, + expected: true, + }, + { + description: "Disabled, No Syncer", + given: config.BidderInfo{Enabled: false, Syncer: nil}, + expected: false, + }, + { + description: "Disabled, Syncer", + given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Key: "anyKey"}}, + expected: false, + }, + { + description: "Disabled, Syncer - Fully Loaded", + given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Key: "anyKey", Default: "iframe", Supports: anySupports, IFrame: anyEndpoint, Redirect: anyEndpoint, SupportCORS: &anyCORS}}, + expected: false, + }, + { + description: "Disabled, Syncer - Only Key", + given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Key: "anyKey"}}, + expected: false, + }, + { + description: "Disabled, Syncer - Only Default", + given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Default: "iframe"}}, + expected: false, + }, + { + description: "Disabled, Syncer - Only Supports", + given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Supports: anySupports}}, + expected: false, + }, + { + description: "Disabled, Syncer - Only IFrame", + given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{IFrame: anyEndpoint}}, + expected: false, + }, + { + description: "Disabled, Syncer - Only Redirect", + given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{Redirect: anyEndpoint}}, + expected: false, + }, + { + description: "Disabled, Syncer - Only SupportCORS", + given: config.BidderInfo{Enabled: false, Syncer: &config.Syncer{SupportCORS: &anyCORS}}, + expected: false, + }, + } + + for _, test := range testCases { + result := shouldCreateSyncer(test.given) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestChooseSyncerConfig(t *testing.T) { + var ( + bidderAPopulated = namedSyncerConfig{name: "bidderA", cfg: config.Syncer{Key: "a", IFrame: &config.SyncerEndpoint{URL: "anyURL"}}} + bidderAEmpty = namedSyncerConfig{name: "bidderA", cfg: config.Syncer{}} + bidderBPopulated = namedSyncerConfig{name: "bidderB", cfg: config.Syncer{Key: "a", IFrame: &config.SyncerEndpoint{URL: "anyURL"}}} + bidderBEmpty = namedSyncerConfig{name: "bidderB", cfg: config.Syncer{}} + ) + + testCases := []struct { + description string + given []namedSyncerConfig + expectedConfig namedSyncerConfig + expectedError string + }{ + { + description: "One", + given: []namedSyncerConfig{bidderAPopulated}, + expectedConfig: bidderAPopulated, + }, + { + description: "Many - Same Key - Unique Configs", + given: []namedSyncerConfig{bidderAEmpty, bidderBPopulated}, + expectedConfig: bidderBPopulated, + }, + { + description: "Many - Same Key - Multiple Configs", + given: []namedSyncerConfig{bidderAPopulated, bidderBPopulated}, + expectedError: "bidders bidderA, bidderB define endpoints (iframe and/or redirect) for the same syncer key, but only one bidder is permitted to define endpoints", + }, + { + description: "Many - Same Key - No Configs", + given: []namedSyncerConfig{bidderAEmpty, bidderBEmpty}, + expectedError: "bidders bidderA, bidderB share the same syncer key, but none define endpoints (iframe and/or redirect)", + }, + { + description: "Many - Same Key - Unique Configs", + given: []namedSyncerConfig{bidderAEmpty, bidderBPopulated}, + expectedConfig: bidderBPopulated, + }, + } + + for _, test := range testCases { + result, err := chooseSyncerConfig(test.given) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + assert.Equal(t, test.expectedConfig, result, test.description+":result") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + assert.Empty(t, result, test.description+":result") + } + } +} diff --git a/usersync/synctype.go b/usersync/synctype.go new file mode 100644 index 00000000000..c9e59f360de --- /dev/null +++ b/usersync/synctype.go @@ -0,0 +1,56 @@ +package usersync + +import ( + "fmt" + "strings" +) + +// SyncType specifies the mechanism used to perform a user sync. +type SyncType string + +const ( + // SyncTypeUnknown specifies the user sync type is invalid or not specified. + SyncTypeUnknown SyncType = "" + + // SyncTypeIFrame specifies the user sync is to be performed within an HTML iframe + // and to expect the server to return a valid HTML page with an embedded script. + SyncTypeIFrame SyncType = "iframe" + + // SyncTypeRedirect specifies the user sync is to be performed within an HTML image + // and to expect the server to return a 302 redirect. + SyncTypeRedirect SyncType = "redirect" +) + +// SyncTypeParse returns the SyncType parsed from a string, case insensitive. +func SyncTypeParse(v string) (SyncType, error) { + if strings.EqualFold(v, string(SyncTypeIFrame)) { + return SyncTypeIFrame, nil + } + + if strings.EqualFold(v, string(SyncTypeRedirect)) { + return SyncTypeRedirect, nil + } + + return SyncTypeUnknown, fmt.Errorf("invalid sync type `%s`", v) +} + +// SyncTypeFilter determines which sync types, if any, the bidder is permitted to use. +type SyncTypeFilter struct { + IFrame BidderFilter + Redirect BidderFilter +} + +// ForBidder returns a slice of sync types the bidder is permitted to use. +func (t SyncTypeFilter) ForBidder(bidder string) []SyncType { + var syncTypes []SyncType + + if t.IFrame.Allowed(bidder) { + syncTypes = append(syncTypes, SyncTypeIFrame) + } + + if t.Redirect.Allowed(bidder) { + syncTypes = append(syncTypes, SyncTypeRedirect) + } + + return syncTypes +} diff --git a/usersync/synctype_test.go b/usersync/synctype_test.go new file mode 100644 index 00000000000..35a241f182b --- /dev/null +++ b/usersync/synctype_test.go @@ -0,0 +1,98 @@ +package usersync + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSyncTypeFilter(t *testing.T) { + bidder := "foo" + + bidderFilterAllowed := NewUniformBidderFilter(BidderFilterModeInclude) + bidderFilterNotAllowed := NewUniformBidderFilter(BidderFilterModeExclude) + + testCases := []struct { + description string + givenIFrameFilter BidderFilter + givenRedirectFilter BidderFilter + expectedSyncTypes []SyncType + }{ + { + description: "None", + givenIFrameFilter: bidderFilterNotAllowed, + givenRedirectFilter: bidderFilterNotAllowed, + expectedSyncTypes: []SyncType{}, + }, + { + description: "IFrame Only", + givenIFrameFilter: bidderFilterAllowed, + givenRedirectFilter: bidderFilterNotAllowed, + expectedSyncTypes: []SyncType{SyncTypeIFrame}, + }, + { + description: "Redirect Only", + givenIFrameFilter: bidderFilterNotAllowed, + givenRedirectFilter: bidderFilterAllowed, + expectedSyncTypes: []SyncType{SyncTypeRedirect}, + }, + { + description: "All", + givenIFrameFilter: bidderFilterAllowed, + givenRedirectFilter: bidderFilterAllowed, + expectedSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, + }, + } + + for _, test := range testCases { + syncTypeFilter := SyncTypeFilter{IFrame: test.givenIFrameFilter, Redirect: test.givenRedirectFilter} + syncTypes := syncTypeFilter.ForBidder(bidder) + assert.ElementsMatch(t, test.expectedSyncTypes, syncTypes, test.description) + } +} + +func TestSyncTypeParse(t *testing.T) { + testCases := []struct { + description string + given string + expected SyncType + expectedError string + }{ + { + description: "IFrame", + given: "iframe", + expected: SyncTypeIFrame, + }, + { + description: "IFrame - Case Insensitive", + given: "iFrAmE", + expected: SyncTypeIFrame, + }, + { + description: "Redirect", + given: "redirect", + expected: SyncTypeRedirect, + }, + { + description: "Redirect - Case Insensitive", + given: "ReDiReCt", + expected: SyncTypeRedirect, + }, + { + description: "Invalid", + given: "invalid", + expectedError: "invalid sync type `invalid`", + }, + } + + for _, test := range testCases { + result, err := SyncTypeParse(test.given) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + assert.Equal(t, test.expected, result, test.description+":result") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + } +} diff --git a/usersync/usersync.go b/usersync/usersync.go deleted file mode 100644 index dffc1832f01..00000000000 --- a/usersync/usersync.go +++ /dev/null @@ -1,29 +0,0 @@ -package usersync - -import "github.com/prebid/prebid-server/privacy" - -type Usersyncer interface { - // GetUsersyncInfo returns basic info the browser needs in order to run a user sync. - // The returned UsersyncInfo object must not be mutated by callers. - // - // For more information about user syncs, see http://clearcode.cc/2015/12/cookie-syncing/ - GetUsersyncInfo(privacyPolicies privacy.Policies) (*UsersyncInfo, error) - - // FamilyName should be the same as the `BidderName` for this Usersyncer. - // This function only exists for legacy reasons. - // TODO #362: when the appnexus usersyncer is consistent, delete this and use the key - // of NewSyncerMap() here instead. - FamilyName() string -} - -type UsersyncInfo struct { - URL string `json:"url,omitempty"` - Type string `json:"type,omitempty"` - SupportCORS bool `json:"supportCORS,omitempty"` -} - -type CookieSyncBidders struct { - BidderCode string `json:"bidder"` - NoCookie bool `json:"no_cookie,omitempty"` - UsersyncInfo *UsersyncInfo `json:"usersync,omitempty"` -} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go deleted file mode 100644 index 87f8cf258f0..00000000000 --- a/usersync/usersyncers/syncer.go +++ /dev/null @@ -1,221 +0,0 @@ -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" - "github.com/prebid/prebid-server/adapters/adman" - "github.com/prebid/prebid-server/adapters/admixer" - "github.com/prebid/prebid-server/adapters/adocean" - "github.com/prebid/prebid-server/adapters/adpone" - "github.com/prebid/prebid-server/adapters/adtarget" - "github.com/prebid/prebid-server/adapters/adtelligent" - "github.com/prebid/prebid-server/adapters/advangelists" - "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/amx" - "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/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" - "github.com/prebid/prebid-server/adapters/consumable" - "github.com/prebid/prebid-server/adapters/conversant" - "github.com/prebid/prebid-server/adapters/cpmstar" - "github.com/prebid/prebid-server/adapters/criteo" - "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" - "github.com/prebid/prebid-server/adapters/gamma" - "github.com/prebid/prebid-server/adapters/gamoshi" - "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/lockerdome" - "github.com/prebid/prebid-server/adapters/logicad" - "github.com/prebid/prebid-server/adapters/lunamedia" - "github.com/prebid/prebid-server/adapters/marsmedia" - "github.com/prebid/prebid-server/adapters/mediafuse" - "github.com/prebid/prebid-server/adapters/mgid" - "github.com/prebid/prebid-server/adapters/nanointeractive" - "github.com/prebid/prebid-server/adapters/ninthdecimal" - "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/outbrain" - "github.com/prebid/prebid-server/adapters/pubmatic" - "github.com/prebid/prebid-server/adapters/pulsepoint" - "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/smarthub" - "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" - "github.com/prebid/prebid-server/adapters/synacormedia" - "github.com/prebid/prebid-server/adapters/tappx" - "github.com/prebid/prebid-server/adapters/telaria" - "github.com/prebid/prebid-server/adapters/triplelift" - "github.com/prebid/prebid-server/adapters/triplelift_native" - "github.com/prebid/prebid-server/adapters/trustx" - "github.com/prebid/prebid-server/adapters/ucfunnel" - "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" - "github.com/prebid/prebid-server/adapters/yieldmo" - "github.com/prebid/prebid-server/adapters/yieldone" - "github.com/prebid/prebid-server/adapters/zeroclickfraud" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/usersync" -) - -// NewSyncerMap returns a map of all the usersyncer objects. -// The same keys should exist in this map as in the exchanges map. -// Static syncer map will be removed when adapter isolation is complete. -func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync.Usersyncer { - syncers := make(map[openrtb_ext.BidderName]usersync.Usersyncer, len(cfg.Adapters)) - - 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) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAdman, adman.NewAdmanSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAdmixer, admixer.NewAdmixerSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAdOcean, adocean.NewAdOceanSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAdpone, adpone.NewadponeSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtarget, adtarget.NewAdtargetSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtelligent, adtelligent.NewAdtelligentSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAdvangelists, advangelists.NewAdvangelistsSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAdxcg, adxcg.NewAdxcgSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAdyoulike, adyoulike.NewAdyoulikeSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAJA, aja.NewAJASyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAMX, amx.NewAMXSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderAppnexus, appnexus.NewAppnexusSyncer) - 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) - insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderCriteo, criteo.NewCriteoSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer) - 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) - insertIntoMap(cfg, syncers, openrtb_ext.BidderGamma, gamma.NewGammaSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderGamoshi, gamoshi.NewGamoshiSyncer) - 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.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) - insertIntoMap(cfg, syncers, openrtb_ext.BidderNanoInteractive, nanointeractive.NewNanoInteractiveSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderNinthDecimal, ninthdecimal.NewNinthDecimalSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderNoBid, nobid.NewNoBidSyncer) - 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) - insertIntoMap(cfg, syncers, openrtb_ext.BidderRTBHouse, rtbhouse.NewRTBHouseSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderRubicon, rubicon.NewRubiconSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderSharethrough, sharethrough.NewSharethroughSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderSomoaudience, somoaudience.NewSomoaudienceSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderSonobi, sonobi.NewSonobiSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderSovrn, sovrn.NewSovrnSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartAdserver, smartadserver.NewSmartadserverSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartHub, smarthub.NewSmartHubSyncer) - 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) - insertIntoMap(cfg, syncers, openrtb_ext.BidderTriplelift, triplelift.NewTripleliftSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderTrustX, trustx.NewTrustXSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderUcfunnel, ucfunnel.NewUcfunnelSyncer) - 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) - insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldmo, yieldmo.NewYieldmoSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldone, yieldone.NewYieldoneSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderZeroClickFraud, zeroclickfraud.NewZeroClickFraudSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderBetween, between.NewBetweenSyncer) - - return syncers -} - -func insertIntoMap(cfg *config.Configuration, syncers map[openrtb_ext.BidderName]usersync.Usersyncer, bidder openrtb_ext.BidderName, syncerFactory func(*template.Template) usersync.Usersyncer) { - lowercased := strings.ToLower(string(bidder)) - urlString := cfg.Adapters[lowercased].UserSyncURL - if urlString == "" { - glog.Warningf("adapters." + string(bidder) + ".usersync_url was not defined, and their usersync API isn't flexible enough for Prebid Server to choose a good default. No usersyncs will be performed with " + string(bidder)) - return - } - syncers[bidder] = syncerFactory(template.Must(template.New(lowercased + "_usersync_url").Parse(urlString))) -} diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go deleted file mode 100644 index 4a6ffb96c99..00000000000 --- a/usersync/usersyncers/syncer_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package usersyncers - -import ( - "strings" - "testing" - - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" -) - -func TestNewSyncerMap(t *testing.T) { - syncConfig := config.Adapter{ - UserSyncURL: "some-sync-url", - } - cfg := &config.Configuration{ - 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, - string(openrtb_ext.BidderAdman): syncConfig, - string(openrtb_ext.BidderAdmixer): syncConfig, - string(openrtb_ext.BidderAdOcean): syncConfig, - string(openrtb_ext.BidderAdpone): syncConfig, - string(openrtb_ext.BidderAdtarget): syncConfig, - string(openrtb_ext.BidderAdtelligent): syncConfig, - string(openrtb_ext.BidderAdvangelists): syncConfig, - string(openrtb_ext.BidderAdxcg): syncConfig, - string(openrtb_ext.BidderAdyoulike): syncConfig, - string(openrtb_ext.BidderAJA): syncConfig, - string(openrtb_ext.BidderAMX): syncConfig, - string(openrtb_ext.BidderAppnexus): syncConfig, - string(openrtb_ext.BidderAudienceNetwork): syncConfig, - string(openrtb_ext.BidderAvocet): syncConfig, - 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, - string(openrtb_ext.BidderConsumable): syncConfig, - string(openrtb_ext.BidderConversant): syncConfig, - string(openrtb_ext.BidderCpmstar): syncConfig, - string(openrtb_ext.BidderCriteo): syncConfig, - string(openrtb_ext.BidderDatablocks): syncConfig, - string(openrtb_ext.BidderDmx): syncConfig, - string(openrtb_ext.BidderDeepintent): syncConfig, - 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.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, - string(openrtb_ext.BidderNanoInteractive): syncConfig, - string(openrtb_ext.BidderNinthDecimal): syncConfig, - 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, - string(openrtb_ext.BidderRhythmone): syncConfig, - string(openrtb_ext.BidderRTBHouse): syncConfig, - string(openrtb_ext.BidderRubicon): syncConfig, - string(openrtb_ext.BidderSharethrough): syncConfig, - string(openrtb_ext.BidderSmartAdserver): syncConfig, - string(openrtb_ext.BidderSmartHub): 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, - string(openrtb_ext.BidderSynacormedia): syncConfig, - string(openrtb_ext.BidderTappx): syncConfig, - string(openrtb_ext.BidderTelaria): syncConfig, - string(openrtb_ext.BidderTriplelift): syncConfig, - string(openrtb_ext.BidderTripleliftNative): syncConfig, - string(openrtb_ext.BidderTrustX): syncConfig, - string(openrtb_ext.BidderUcfunnel): syncConfig, - 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, - string(openrtb_ext.BidderYieldmo): syncConfig, - string(openrtb_ext.BidderYieldone): syncConfig, - string(openrtb_ext.BidderZeroClickFraud): syncConfig, - }, - } - - 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.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.BidderIQZone: 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.BidderUnicorn: true, - openrtb_ext.BidderYeahmobi: true, - } - - for bidder, config := range cfg.Adapters { - delete(cfg.Adapters, bidder) - cfg.Adapters[strings.ToLower(string(bidder))] = config - } - - syncers := NewSyncerMap(cfg) - for _, bidderName := range openrtb_ext.CoreBidderNames() { - _, adapterWithoutSyncer := adaptersWithoutSyncers[bidderName] - if _, ok := syncers[bidderName]; !ok && !adapterWithoutSyncer { - t.Errorf("No syncer exists for adapter: %s", bidderName) - } - } -} diff --git a/util/httputil/pixel.go b/util/httputil/pixel.go new file mode 100644 index 00000000000..0beefdb1924 --- /dev/null +++ b/util/httputil/pixel.go @@ -0,0 +1,16 @@ +package httputil + +type Pixel struct { + Content []byte + ContentType string +} + +var Pixel1x1PNG = Pixel{ + Content: []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, + 0x89, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64, 0x88, + 0x00, 0x00, 0x00, 0x0D, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, + 0x00, 0x05, 0x00, 0x01, 0x87, 0xA1, 0x4E, 0xD4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, + 0x42, 0x60, 0x82}, + ContentType: "image/png", +} diff --git a/util/sliceutil/sliceutil.go b/util/sliceutil/sliceutil.go new file mode 100644 index 00000000000..523d35e0980 --- /dev/null +++ b/util/sliceutil/sliceutil.go @@ -0,0 +1,14 @@ +package sliceutil + +import ( + "strings" +) + +func ContainsStringIgnoreCase(s []string, v string) bool { + for _, i := range s { + if strings.EqualFold(i, v) { + return true + } + } + return false +} diff --git a/util/sliceutil/sliceutil_test.go b/util/sliceutil/sliceutil_test.go new file mode 100644 index 00000000000..62936605901 --- /dev/null +++ b/util/sliceutil/sliceutil_test.go @@ -0,0 +1,70 @@ +package sliceutil + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContainsStringIgnoreCase(t *testing.T) { + testCases := []struct { + description string + givenSlice []string + givenValue string + expected bool + }{ + { + description: "Nil", + givenSlice: nil, + givenValue: "a", + expected: false, + }, + { + description: "Empty", + givenSlice: []string{}, + givenValue: "a", + expected: false, + }, + { + description: "One - Match - Same Case", + givenSlice: []string{"a"}, + givenValue: "a", + expected: true, + }, + { + description: "One - Match - Different Case", + givenSlice: []string{"a"}, + givenValue: "A", + expected: true, + }, + { + description: "One - No Match", + givenSlice: []string{"a"}, + givenValue: "z", + expected: false, + }, + { + description: "Many - Match - Same Case", + givenSlice: []string{"a", "b"}, + givenValue: "b", + expected: true, + }, + { + description: "Many - Match - Different Case", + givenSlice: []string{"a", "b"}, + givenValue: "B", + expected: true, + }, + { + description: "Many - No Match", + givenSlice: []string{"a", "b"}, + givenValue: "z", + expected: false, + }, + } + + for _, test := range testCases { + result := ContainsStringIgnoreCase(test.givenSlice, test.givenValue) + assert.Equal(t, test.expected, result, test.description) + } +} From 7beb2cbcd1e4072accd02a263679fcdfec83787e Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Fri, 20 Aug 2021 11:44:23 -0400 Subject: [PATCH 067/140] Update Viper (#1969) --- config/config_test.go | 104 +++++--- go.mod | 16 +- go.sum | 556 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 603 insertions(+), 73 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index 287d57ede26..2e889a79d0f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -11,6 +11,7 @@ import ( "encoding/json" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" "github.com/spf13/viper" "github.com/stretchr/testify/assert" @@ -213,6 +214,9 @@ func TestDefaults(t *testing.T) { }, } assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2") + + // Assert User Sync Override Defaults To Nil + assert.Nil(t, cfg.Adapters["appnexus"].Syncer, "User Sync") } var fullConfig = []byte(` @@ -323,10 +327,13 @@ adapters: endpoint: http://ixtest.com/api rubicon: endpoint: http://rubitest.com/api - usersync_url: http://pixel.rubiconproject.com/sync.php?p=prebid xapi: username: rubiuser password: rubipw23 + usersync: + redirect: + url: http://rubitest.com/sync + user_macro: "{UID}" brightroll: usersync_url: http://test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url=%s endpoint: http://test-bid.ybp.yahoo.com/bid/appnexuspbs @@ -354,38 +361,16 @@ adapters: tracker: anxsTrack disabled: true extra_info: "{\"native\":\"http://www.native.org/endpoint\",\"video\":\"http://www.video.org/endpoint\"}" - audienceNetwork: - endpoint: http://facebook.com/pbs - usersync_url: http://facebook.com/ortb/prebid-s2s - platform_id: abcdefgh1234 - ix: - endpoint: http://ixtest.com/api - rubicon: - endpoint: http://rubitest.com/api - usersync_url: http://pixel.rubiconproject.com/sync.php?p=prebid - xapi: - username: rubiuser - password: rubipw23 - brightroll: - usersync_url: http://test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&url=%s - endpoint: http://test-bid.ybp.yahoo.com/bid/appnexuspbs - adkerneladn: - usersync_url: https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r= -blacklisted_apps: ["spamAppID","sketchy-app-id"] `) var invalidAdapterEndpointConfig = []byte(` adapters: appnexus: endpoint: ib.adnxs.com/some/endpoint - audienceNetwork: - endpoint: http://facebook.com/pbs - usersync_url: http://facebook.com/ortb/prebid-s2s - platform_id: abcdefgh1234 brightroll: - usersync_url: http://http://test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&url=%s - adkerneladn: - usersync_url: https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r= + usersync: + redirect: + url: http://http://test-bh.ybp.yahoo.com/sync/appnexuspbs?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&url=%s `) var oldStoredRequestsConfig = []byte(` @@ -409,6 +394,11 @@ func cmpBools(t *testing.T, key string, a bool, b bool) { assert.Equal(t, a, b, "%s: %t != %t", key, a, b) } +func cmpNils(t *testing.T, key string, a interface{}) { + t.Helper() + assert.Nilf(t, a, "%s: %t != nil", key, a) +} + func TestFullConfig(t *testing.T) { v := viper.New() SetupViper(v, "") @@ -565,12 +555,16 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "adapters.audiencenetwork.endpoint", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAudienceNetwork))].Endpoint, "http://facebook.com/pbs") cmpStrings(t, "adapters.audiencenetwork.platform_id", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAudienceNetwork))].PlatformID, "abcdefgh1234") cmpStrings(t, "adapters.audiencenetwork.app_secret", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAudienceNetwork))].AppSecret, "987abc") + cmpStrings(t, "adapters.audiencenetwork.usersync_url", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderAudienceNetwork))].UserSyncURL, "http://facebook.com/ortb/prebid-s2s") cmpStrings(t, "adapters.beachfront.endpoint", cfg.Adapters[string(openrtb_ext.BidderBeachfront)].Endpoint, "https://display.bfmio.com/prebid_display") cmpStrings(t, "adapters.beachfront.extra_info", cfg.Adapters[string(openrtb_ext.BidderBeachfront)].ExtraAdapterInfo, "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") cmpStrings(t, "adapters.ix.endpoint", cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint, "http://ixtest.com/api") cmpStrings(t, "adapters.rubicon.endpoint", cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, "http://rubitest.com/api") cmpStrings(t, "adapters.rubicon.xapi.username", cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Username, "rubiuser") cmpStrings(t, "adapters.rubicon.xapi.password", cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Password, "rubipw23") + cmpStrings(t, "adapters.rubicon.usersync.redirect.url", cfg.Adapters[string(openrtb_ext.BidderRubicon)].Syncer.Redirect.URL, "http://rubitest.com/sync") + cmpNils(t, "adapters.rubicon.usersync.iframe", cfg.Adapters[string(openrtb_ext.BidderRubicon)].Syncer.IFrame) + cmpStrings(t, "adapters.rubicon.usersync.redirect.user_macro", cfg.Adapters[string(openrtb_ext.BidderRubicon)].Syncer.Redirect.UserMacro, "{UID}") cmpStrings(t, "adapters.brightroll.endpoint", cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint, "http://test-bid.ybp.yahoo.com/bid/appnexuspbs") cmpStrings(t, "adapters.rhythmone.endpoint", cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint, "http://tag.1rx.io/rmp") cmpBools(t, "account_required", cfg.AccountRequired, true) @@ -597,9 +591,6 @@ func TestUnmarshalAdapterExtraInfo(t *testing.T) { // Assert correctly unmarshaled assert.NoError(t, err, "invalid endpoint in config should return an error") - // Unescape quotes of JSON-formatted string - strings.Replace(cfg.Adapters[string(openrtb_ext.BidderAppnexus)].ExtraAdapterInfo, "\\\"", "\"", -1) - // Assert JSON-formatted string assert.JSONEqf(t, `{"native":"http://www.native.org/endpoint","video":"http://www.video.org/endpoint"}`, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].ExtraAdapterInfo, "Unexpected value of the ExtraAdapterInfo String \n") @@ -619,7 +610,7 @@ func TestUnmarshalAdapterExtraInfo(t *testing.T) { assert.Equal(t, "http://www.video.org/endpoint", AppNexusAdapterExtraInfo.VideoEndpoint) } -func TestValidConfig(t *testing.T) { +func TestValidateConfig(t *testing.T) { cfg := Configuration{ GDPR: GDPR{ DefaultValue: "1", @@ -757,7 +748,11 @@ func TestInvalidAdapterEndpointConfig(t *testing.T) { v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(invalidAdapterEndpointConfig)) _, err := New(v) - assert.Error(t, err, "invalid endpoint in config should return an error") + + if assert.IsType(t, errortypes.AggregateError{}, err) { + aggErr := err.(errortypes.AggregateError) + assert.ElementsMatch(t, []error{errors.New("The endpoint: ib.adnxs.com/some/endpoint for appnexus is not a valid URL")}, aggErr.Errors) + } } func TestNegativeRequestSize(t *testing.T) { @@ -936,6 +931,53 @@ func TestValidateAccountsConfigRestrictions(t *testing.T) { assert.Contains(t, errs, errors.New("accounts.postgres: retrieving accounts via postgres not available, use accounts.files")) } +func TestUserSyncFromEnv(t *testing.T) { + truePtr := true + + // setup env vars for testing + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_APPNEXUS_USERSYNC_REDIRECT_URL"); ok { + defer os.Setenv("PBS_ADAPTERS_APPNEXUS_USERSYNC_REDIRECT_URL", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_APPNEXUS_USERSYNC_REDIRECT_URL") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_APPNEXUS_USERSYNC_REDIRECT_USER_MACRO"); ok { + defer os.Setenv("PBS_ADAPTERS_APPNEXUS_USERSYNC_REDIRECT_USER_MACRO", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_APPNEXUS_USERSYNC_REDIRECT_USER_MACRO") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_APPNEXUS_USERSYNC_SUPPORT_CORS"); ok { + defer os.Setenv("PBS_ADAPTERS_APPNEXUS_USERSYNC_SUPPORT_CORS", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_APPNEXUS_USERSYNC_SUPPORT_CORS") + } + + if oldval, ok := os.LookupEnv("PBS_ADAPTERS_RUBICON_USERSYNC_IFRAME_URL"); ok { + defer os.Setenv("PBS_ADAPTERS_RUBICON_USERSYNC_IFRAME_URL", oldval) + } else { + defer os.Unsetenv("PBS_ADAPTERS_RUBICON_USERSYNC_IFRAME_URL") + } + + // set new + os.Setenv("PBS_ADAPTERS_APPNEXUS_USERSYNC_REDIRECT_URL", "http://some.url/sync?redirect={{.RedirectURL}}") + os.Setenv("PBS_ADAPTERS_APPNEXUS_USERSYNC_REDIRECT_USER_MACRO", "[UID]") + os.Setenv("PBS_ADAPTERS_APPNEXUS_USERSYNC_SUPPORT_CORS", "true") + os.Setenv("PBS_ADAPTERS_RUBICON_USERSYNC_IFRAME_URL", "http://somedifferent.url/sync?redirect={{.RedirectURL}}") + + cfg, _ := newDefaultConfig(t) + assert.Equal(t, cfg.Adapters["appnexus"].Syncer.Redirect.URL, "http://some.url/sync?redirect={{.RedirectURL}}") + assert.Equal(t, cfg.Adapters["appnexus"].Syncer.Redirect.UserMacro, "[UID]") + assert.Nil(t, cfg.Adapters["appnexus"].Syncer.IFrame) + assert.Equal(t, cfg.Adapters["appnexus"].Syncer.SupportCORS, &truePtr) + + assert.Equal(t, cfg.Adapters["rubicon"].Syncer.IFrame.URL, "http://somedifferent.url/sync?redirect={{.RedirectURL}}") + assert.Nil(t, cfg.Adapters["rubicon"].Syncer.Redirect) + assert.Nil(t, cfg.Adapters["rubicon"].Syncer.SupportCORS) + + assert.Nil(t, cfg.Adapters["brightroll"].Syncer) +} + func newDefaultConfig(t *testing.T) (*Configuration, *viper.Viper) { v := viper.New() SetupViper(v, "") diff --git a/go.mod b/go.mod index 6193e08c47c..d003abae24d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/prebid/prebid-server go 1.16 require ( - github.com/BurntSushi/toml v0.3.1 // indirect github.com/DATA-DOG/go-sqlmock v1.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/OneOfOne/xxhash v1.2.5 // indirect @@ -19,31 +18,24 @@ require ( github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b - github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb v1.6.1 github.com/julienschmidt/httprouter v1.1.0 github.com/lib/pq v1.0.0 - github.com/magiconair/properties v1.8.0 + github.com/magiconair/properties v1.8.5 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.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/client_model v0.0.0-20190812154241-14fe0d1b01d4 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 // indirect github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 github.com/rs/cors v1.5.0 github.com/sergi/go-diff v1.0.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.1.1 // indirect - github.com/spf13/cast v1.2.0 // indirect - github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 // indirect - github.com/spf13/pflag v1.0.2 // indirect - github.com/spf13/viper v1.1.0 + github.com/spf13/viper v1.8.1 github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.7.0 github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 @@ -53,7 +45,7 @@ require ( github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d 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/net v0.0.0-20210405180319-a5a99cb37ef4 golang.org/x/text v0.3.6 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index a4cab742c34..343bcf0d8f8 100644 --- a/go.sum +++ b/go.sum @@ -1,135 +1,316 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.0 h1:ljjRxlddjfChBJdFKJs5LuCwCWPLaC1UZLwAo3PBBMk= github.com/DATA-DOG/go-sqlmock v1.3.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c/go.mod h1:omJZNg0Qu76bxJd+ExohVo8uXzNcGOk2bv7vel460xk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coocood/freecache v1.0.1 h1:oFyo4msX2c0QIKU+kuMJUwsKamJ+AKc2JJrKcMszJ5M= github.com/coocood/freecache v1.0.1/go.mod h1:ePwxCDzOYvARfHdr1pByNct1at3CoKnsipOHwKlNbzI= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/influxdata/influxdb v1.6.1 h1:OseoBlzI5ftNI/bczyxSWq6PKRCNEeiXvyWP/wS5fB0= github.com/influxdata/influxdb v1.6.1/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4EI5MABq68= github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 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/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 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= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.5.0 h1:dgSHE6+ia18arGOTIYQKKGWLvEbGvmbNE6NfxhoNHUY= github.com/rs/cors v1.5.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I= -github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= -github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= -github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 h1:kJI9pPzfsULT/72wy7mxkRQZPtKWgFdCA2RTGZ4v8/E= -github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.1.0 h1:V7OZpY8i3C1x/pDmU0zNNlfVoDz112fSYvtWMjjS3f4= -github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 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= @@ -144,60 +325,375 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/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= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 6f542fbb0c48bf2c1413038909a49c120b31b05a Mon Sep 17 00:00:00 2001 From: hhhjort <31041505+hhhjort@users.noreply.github.com> Date: Tue, 24 Aug 2021 16:52:04 -0400 Subject: [PATCH 068/140] Fixes accidental change in the cookiesync API for te gdpr flag (#1975) --- endpoints/cookie_sync.go | 11 ++++++++--- endpoints/cookie_sync_test.go | 10 +++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index f6ec8152870..079348be218 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -7,6 +7,7 @@ import ( "fmt" "io/ioutil" "net/http" + "strconv" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -105,7 +106,11 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr return usersync.Request{}, privacy.Policies{}, fmt.Errorf("JSON parsing failed: %s", err.Error()) } - gdprSignal, err := gdpr.SignalParse(request.GDPR) + var gdprString string + if request.GDPR != nil { + gdprString = strconv.Itoa(*request.GDPR) + } + gdprSignal, err := gdpr.SignalParse(gdprString) if err != nil { return usersync.Request{}, privacy.Policies{}, err } @@ -122,7 +127,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr privacyPolicies := privacy.Policies{ GDPR: gdprPrivacy.Policy{ - Signal: request.GDPR, + Signal: gdprString, Consent: request.GDPRConsent, }, CCPA: ccpa.Policy{ @@ -308,7 +313,7 @@ func mapBidderStatusToAnalytics(from []cookieSyncResponseBidder) []*analytics.Co type cookieSyncRequest struct { Bidders []string `json:"bidders"` - GDPR string `json:"gdpr"` + GDPR *int `json:"gdpr"` GDPRConsent string `json:"gdpr_consent"` USPrivacy string `json:"us_privacy"` Limit int `json:"limit"` diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 2ef2eb1770b..466618d86c6 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -276,7 +276,7 @@ func TestCookieSyncParseRequest(t *testing.T) { description: "Complete Request", givenBody: strings.NewReader(`{` + `"bidders":["a", "b"],` + - `"gdpr":"1",` + + `"gdpr":1,` + `"gdpr_consent":"anyGDPRConsent",` + `"us_privacy":"1NYN",` + `"limit":42,` + @@ -322,7 +322,7 @@ func TestCookieSyncParseRequest(t *testing.T) { description: "Complete Request - Legacy Fields Only", givenBody: strings.NewReader(`{` + `"bidders":["a", "b"],` + - `"gdpr":"1",` + + `"gdpr":1,` + `"gdpr_consent":"anyGDPRConsent",` + `"us_privacy":"1NYN",` + `"limit":42` + @@ -585,14 +585,14 @@ func TestCookieSyncParseRequest(t *testing.T) { }, { description: "Invalid GDPR Signal", - givenBody: strings.NewReader(`{"gdpr":"invalid"}`), + givenBody: strings.NewReader(`{"gdpr":5}`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, expectedError: "GDPR signal should be integer 0 or 1", }, { description: "Missing GDPR Consent - Explicit Signal 0", - givenBody: strings.NewReader(`{"gdpr":"0"}`), + givenBody: strings.NewReader(`{"gdpr":0}`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, expectedPrivacy: privacy.Policies{ @@ -610,7 +610,7 @@ func TestCookieSyncParseRequest(t *testing.T) { }, { description: "Missing GDPR Consent - Explicit Signal 1", - givenBody: strings.NewReader(`{"gdpr":"1"}`), + givenBody: strings.NewReader(`{"gdpr":1}`), givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, givenCCPAEnabled: true, expectedError: "gdpr_consent is required if gdpr=1", From 498d2cfa0492c8c0a0eddf85349bf19db40c0df5 Mon Sep 17 00:00:00 2001 From: wy <1402628279@qq.com> Date: Wed, 25 Aug 2021 22:01:00 +0400 Subject: [PATCH 069/140] New Adapter: HuaweiAds (#1905) * New Adapter: HuaweiAds * add usersync, no support * modify native * modify usersync, just pass * modify code for banner native * modify code for banner, monitor url can be a array. * correct some issues * move clientTime, add extraInfo, correct some example, add mccmnc * Optimize some examples * Optimize some examples, native add new trackingUrl * Optimize some examples, add adId creativeId * Optimize some examples, add userclose event type * Optimize some examples, add country, correct * Optimize some examples, add zone * Optimize some examples, add userclose event type * correct issues in the pull request, delete useless parameters * correct issues in the pull request on github * correct issues in the pull request on github, add bad_response_not_native.json * add test authorization, update test cases. * Merge branch 'master' of https://github.com/prebid/prebid-server into prebid-master * correct some issues, delete race files Co-authored-by: w00522253 --- adapters/huaweiads/huaweiads.go | 1261 +++++++++++++++++ adapters/huaweiads/huaweiads_test.go | 18 + .../huaweiadstest/exemplary/banner1.json | 254 ++++ .../huaweiadstest/exemplary/banner2.json | 271 ++++ .../exemplary/nativeIncludeVideo.json | 326 +++++ .../exemplary/nativeSingleImage.json | 315 ++++ .../exemplary/nativeThreeImage.json | 334 +++++ .../nativeThreeImageIncludeIcon.json | 334 +++++ .../huaweiadstest/exemplary/video.json | 338 +++++ .../adtype_roll_missing_duration.json | 68 + .../supplemental/bad_response.json | 228 +++ .../supplemental/bad_response_400.json | 153 ++ .../supplemental/bad_response_503.json | 153 ++ .../bad_response_dont_find_impid.json | 324 +++++ .../supplemental/bad_response_not_native.json | 219 +++ .../bad_response_retcode30_204.json | 162 +++ .../bad_response_retcode_210.json | 156 ++ .../bad_response_retcode_408.json | 156 ++ .../bad_response_retcode_500.json | 156 ++ .../supplemental/missing_adtype.json | 74 + .../supplemental/missing_deviceid.json | 77 + .../supplemental/missing_keyid.json | 78 + .../supplemental/missing_native_request.json | 73 + .../supplemental/missing_pulisherid.json | 78 + .../supplemental/missing_signkey.json | 78 + .../supplemental/missing_slotid.json | 78 + adapters/huaweiads/params_test.go | 53 + config/config.go | 2 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_huaweiads.go | 21 + static/bidder-info/huaweiads.yaml | 9 + static/bidder-params/huaweiads.json | 40 + 33 files changed, 5891 insertions(+) create mode 100644 adapters/huaweiads/huaweiads.go create mode 100644 adapters/huaweiads/huaweiads_test.go create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/banner1.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/banner2.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json create mode 100644 adapters/huaweiads/huaweiadstest/exemplary/video.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/adtype_roll_missing_duration.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_400.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_503.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_dont_find_impid.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode30_204.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_210.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_408.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_500.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_adtype.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_keyid.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_native_request.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_pulisherid.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_signkey.json create mode 100644 adapters/huaweiads/huaweiadstest/supplemental/missing_slotid.json create mode 100644 adapters/huaweiads/params_test.go create mode 100644 openrtb_ext/imp_huaweiads.go create mode 100644 static/bidder-info/huaweiads.yaml create mode 100644 static/bidder-params/huaweiads.json diff --git a/adapters/huaweiads/huaweiads.go b/adapters/huaweiads/huaweiads.go new file mode 100644 index 00000000000..3948442233d --- /dev/null +++ b/adapters/huaweiads/huaweiads.go @@ -0,0 +1,1261 @@ +package huaweiads + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "github.com/mxmCherry/openrtb/v15/native1" + nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" + nativeResponse "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" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +const huaweiAdxApiVersion = "3.4" +const defaultCountryName = "ZA" +const defaultUnknownNetworkType = 0 +const timeFormat = "2006-01-02 15:04:05.000" +const defaultTimeZone = "+0200" +const defaultModelName = "HUAWEI" + +// creative type +const ( + text int32 = 1 + bigPicture int32 = 2 + bigPicture2 int32 = 3 + gif int32 = 4 + videoText int32 = 6 + smallPicture int32 = 7 + threeSmallPicturesText int32 = 8 + video int32 = 9 + iconText int32 = 10 + videoWithPicturesText int32 = 11 +) + +// ads type +const ( + banner int32 = 8 + native int32 = 3 + roll int32 = 60 + rewarded int32 = 7 + splash int32 = 1 + interstitial int32 = 12 +) + +type huaweiAdsRequest struct { + Version string `json:"version"` + Multislot []adslot30 `json:"multislot"` + App app `json:"app"` + Device device `json:"device"` + Network network `json:"network,omitempty"` + Regs regs `json:"regs,omitempty"` + Geo geo `json:"geo,omitempty"` +} + +type adslot30 struct { + Slotid string `json:"slotid"` + Adtype int32 `json:"adtype"` + Test int32 `json:"test"` + TotalDuration int32 `json:"totalDuration,omitempty"` + Orientation int32 `json:"orientation,omitempty"` + W int64 `json:"w,omitempty"` + H int64 `json:"h,omitempty"` + Format []format `json:"format,omitempty"` + DetailedCreativeTypeList []string `json:"detailedCreativeTypeList,omitempty"` +} + +type format struct { + W int64 `json:"w,omitempty"` + H int64 `json:"h,omitempty"` +} + +type app struct { + Version string `json:"version,omitempty"` + Name string `json:"name,omitempty"` + Pkgname string `json:"pkgname"` + Lang string `json:"lang,omitempty"` + Country string `json:"country,omitempty"` +} + +type device struct { + Type int32 `json:"type,omitempty"` + Useragent string `json:"useragent,omitempty"` + Os string `json:"os,omitempty"` + Version string `json:"version,omitempty"` + Maker string `json:"maker,omitempty"` + Model string `json:"model,omitempty"` + Width int32 `json:"width,omitempty"` + Height int32 `json:"height,omitempty"` + Language string `json:"language,omitempty"` + BuildVersion string `json:"buildVersion,omitempty"` + Dpi int32 `json:"dpi,omitempty"` + Pxratio float32 `json:"pxratio,omitempty"` + Imei string `json:"imei,omitempty"` + Oaid string `json:"oaid,omitempty"` + IsTrackingEnabled string `json:"isTrackingEnabled,omitempty"` + EmuiVer string `json:"emuiVer,omitempty"` + LocaleCountry string `json:"localeCountry"` + BelongCountry string `json:"belongCountry"` + GaidTrackingEnabled string `json:"gaidTrackingEnabled,omitempty"` + Gaid string `json:"gaid,omitempty"` + ClientTime string `json:"clientTime"` + Ip string `json:"ip,omitempty"` +} + +type network struct { + Type int32 `json:"type"` + Carrier int32 `json:"carrier,omitempty"` + CellInfo []cellInfo `json:"cellInfo,omitempty"` +} + +type regs struct { + Coppa int32 `json:"coppa,omitempty"` +} + +type geo struct { + Lon float32 `json:"lon,omitempty"` + Lat float32 `json:"lat,omitempty"` + Accuracy int32 `json:"accuracy,omitempty"` + Lastfix int32 `json:"lastfix,omitempty"` +} + +type cellInfo struct { + Mcc string `json:"mcc,omitempty"` + Mnc string `json:"mnc,omitempty"` +} + +type huaweiAdsResponse struct { + Retcode int32 `json:"retcode"` + Reason string `json:"reason"` + Multiad []ad30 `json:"multiad"` +} + +type ad30 struct { + AdType int32 `json:"adtype"` + Slotid string `json:"slotid"` + Retcode30 int32 `json:"retcode30"` + Content []content `json:"content"` +} + +type content struct { + Contentid string `json:"contentid"` + Interactiontype int32 `json:"interactiontype"` + Creativetype int32 `json:"creativetype"` + MetaData metaData `json:"metaData"` + Monitor []monitor `json:"monitor"` + Cur string `json:"cur"` + Price float64 `json:"price"` +} + +type metaData struct { + Title string `json:"title"` + Description string `json:"description"` + ImageInfo []imageInfo `json:"imageInfo"` + Icon []icon `json:"icon"` + ClickUrl string `json:"clickUrl"` + Intent string `json:"intent"` + VideoInfo videoInfo `json:"videoInfo"` + ApkInfo apkInfo `json:"apkInfo"` + Duration int64 `json:"duration"` + MediaFile mediaFile `json:"mediaFile"` +} + +type imageInfo struct { + Url string `json:"url"` + Height int64 `json:"height"` + FileSize int64 `json:"fileSize"` + Sha256 string `json:"sha256"` + ImageType string `json:"imageType"` + Width int64 `json:"width"` +} + +type icon struct { + Url string `json:"url"` + Height int64 `json:"height"` + FileSize int64 `json:"fileSize"` + Sha256 string `json:"sha256"` + ImageType string `json:"imageType"` + Width int64 `json:"width"` +} + +type videoInfo struct { + VideoDownloadUrl string `json:"videoDownloadUrl"` + VideoDuration int32 `json:"videoDuration"` + VideoFileSize int32 `json:"videoFileSize"` + Sha256 string `json:"sha256"` + VideoRatio float32 `json:"videoRatio"` + Width int32 `json:"width"` + Height int32 `json:"height"` +} + +type apkInfo struct { + Url string `json:"url"` + FileSize int64 `json:"fileSize"` + Sha256 string `json:"sha256"` + PackageName string `json:"packageName"` + SecondUrl string `json:"secondUrl"` + AppName string `json:"appName"` + VersionName string `json:"versionName"` + AppDesc string `json:"appDesc"` + AppIcon string `json:"appIcon"` +} + +type mediaFile struct { + Mime string `json:"mime"` + Width int64 `json:"width"` + Height int64 `json:"height"` + FileSize int64 `json:"fileSize"` + Url string `json:"url"` + Sha256 string `json:"sha256"` +} + +type monitor struct { + EventType string `json:"eventType"` + Url []string `json:"url"` +} + +type adapter struct { + endpoint string +} + +func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { + // the upstream code already confirms that there is a non-zero number of impressions + numRequests := len(openRTBRequest.Imp) + var request huaweiAdsRequest + var header http.Header + var multislot = make([]adslot30, 0, numRequests) + + var huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds + for index := 0; index < numRequests; index++ { + var err1 error + huaweiAdsImpExt, err1 = unmarshalExtImpHuaweiAds(&openRTBRequest.Imp[index]) + if err1 != nil { + return nil, []error{err1} + } + + if huaweiAdsImpExt == nil { + return nil, []error{errors.New("UnmarshalExtImpHuaweiAds: huaweiAdsImpExt is nil.")} + } + + adslot30, err := getHuaweiAdsReqAdslot30(huaweiAdsImpExt, &openRTBRequest.Imp[index], openRTBRequest) + if err != nil { + return nil, []error{err} + } + + multislot = append(multislot, adslot30) + } + request.Multislot = multislot + + if err := getHuaweiAdsReqJson(&request, openRTBRequest, huaweiAdsImpExt); err != nil { + return nil, []error{err} + } + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + // our request header's Authorization is changing by time, cannot verify by a certain string, + // use isTestAuthorization = true only when run testcase + var isTestAuthorization = false + if huaweiAdsImpExt != nil && huaweiAdsImpExt.IsTestAuthorization == "true" { + isTestAuthorization = true + } + header = getHeaders(huaweiAdsImpExt, openRTBRequest, isTestAuthorization) + bidRequest := &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.endpoint, + Body: reqJSON, + Headers: header, + } + + return []*adapters.RequestData{bidRequest}, nil +} + +func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) { + httpStatusError := checkRespStatusCode(bidderRawResponse) + if httpStatusError != nil { + return nil, []error{httpStatusError} + } + + var huaweiAdsResponse huaweiAdsResponse + if err := json.Unmarshal(bidderRawResponse.Body, &huaweiAdsResponse); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Unable to parse server response", + }} + } + + if err := checkHuaweiAdsResponseRetcode(huaweiAdsResponse); err != nil { + return nil, []error{err} + } + + bidderResponse, err := a.convertHuaweiAdsResp2BidderResp(&huaweiAdsResponse, openRTBRequest) + if err != nil { + return nil, []error{err} + } + + return bidderResponse, nil +} + +// Builder builds a new instance of the HuaweiAds 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 +} + +// getHeaders: get request header, Authorization -> digest +func getHeaders(huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds, request *openrtb2.BidRequest, isTestAuthorization bool) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + if huaweiAdsImpExt == nil { + return headers + } + headers.Add("Authorization", getDigestAuthorization(huaweiAdsImpExt, isTestAuthorization)) + + if request.Device != nil && len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + return headers +} + +// getHuaweiAdsReqJson: get body json for HuaweiAds request +func getHuaweiAdsReqJson(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest, huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds) error { + request.Version = huaweiAdxApiVersion + var err error + if err = getHuaweiAdsReqAppInfo(request, openRTBRequest); err != nil { + return err + } + if err = getHuaweiAdsReqDeviceInfo(request, openRTBRequest, huaweiAdsImpExt); err != nil { + return err + } + getHuaweiAdsReqNetWorkInfo(request, openRTBRequest) + getHuaweiAdsReqRegsInfo(request, openRTBRequest) + getHuaweiAdsReqGeoInfo(request, openRTBRequest) + return nil +} + +func getHuaweiAdsReqAdslot30(huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds, + openRTBImp *openrtb2.Imp, openRTBRequest *openrtb2.BidRequest) (adslot30, error) { + adtypeLower := strings.ToLower(huaweiAdsImpExt.Adtype) + var adslot30 = adslot30{ + Slotid: huaweiAdsImpExt.SlotId, + Adtype: convertAdtypeString2Integer(adtypeLower), + Test: int32(openRTBRequest.Test), + } + + if openRTBImp.Banner != nil { + if openRTBImp.Banner.W != nil && openRTBImp.Banner.H != nil { + adslot30.W = *openRTBImp.Banner.W + adslot30.H = *openRTBImp.Banner.H + } + if len(openRTBImp.Banner.Format) != 0 { + var formats = make([]format, 0, len(openRTBImp.Banner.Format)) + for _, f := range openRTBImp.Banner.Format { + if f.H != 0 && f.W != 0 { + formats = append(formats, format{f.W, f.H}) + } + } + adslot30.Format = formats + } + } else if openRTBImp.Native != nil { + if err := getNativeFormat(&adslot30, openRTBImp); err != nil { + return adslot30, err + } + } + + // Currently does not support roll type ads, roll ad need TotalDuration + if adtypeLower == "roll" { + if openRTBImp.Video != nil && openRTBImp.Video.MaxDuration >= 0 { + adslot30.TotalDuration = int32(openRTBImp.Video.MaxDuration) + } else { + return adslot30, errors.New("GetHuaweiAdsReqAdslot30: MaxDuration is empty when adtype is roll.") + } + } + return adslot30, nil +} + +func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error { + if openRTBImp.Native.Request == "" { + return errors.New("extractAdmNative: imp.Native.Request is empty") + } + + var nativePayload nativeRequests.Request + if err := json.Unmarshal(json.RawMessage(openRTBImp.Native.Request), &nativePayload); err != nil { + return err + } + + var numImage = 0 + var numVideo = 0 + var width int64 + var height int64 + for _, asset := range nativePayload.Assets { + if asset.Video != nil { + numVideo++ + } + // every image has the same W, H. + if asset.Img != nil { + numImage++ + if asset.Img.H != 0 && asset.Img.W != 0 { + width = asset.Img.W + height = asset.Img.H + } else if asset.Img.WMin != 0 && asset.Img.HMin != 0 { + width = asset.Img.WMin + height = asset.Img.HMin + } + } + } + adslot30.W = width + adslot30.H = height + + var detailedCreativeTypeList = make([]string, 0, 2) + if numVideo >= 1 { + detailedCreativeTypeList = append(detailedCreativeTypeList, "903") + } else if numImage > 1 { + detailedCreativeTypeList = append(detailedCreativeTypeList, "904") + } else if numImage == 1 { + detailedCreativeTypeList = append(detailedCreativeTypeList, "909") + } else { + detailedCreativeTypeList = append(detailedCreativeTypeList, "913", "914") + } + adslot30.DetailedCreativeTypeList = detailedCreativeTypeList + return nil +} + +func convertAdtypeString2Integer(adtypeLower string) int32 { + switch adtypeLower { + case "banner": + return banner + case "native": + return native + case "rewarded": + return rewarded + case "splash": + return splash + case "interstitial": + return interstitial + case "roll": + return roll + default: + return banner + } +} + +// getHuaweiAdsReqAppInfo: get app information for HuaweiAds request +func getHuaweiAdsReqAppInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) error { + var app app + if openRTBRequest.App != nil { + if openRTBRequest.App.Ver != "" { + app.Version = openRTBRequest.App.Ver + } + if openRTBRequest.App.Name != "" { + app.Name = openRTBRequest.App.Name + } + + // bundle cannot be empty, we need package name. + if openRTBRequest.App.Bundle != "" { + app.Pkgname = openRTBRequest.App.Bundle + } else { + return errors.New("HuaweiAdsReqApp: Pkgname is empty.") + } + + if openRTBRequest.App.Content != nil && openRTBRequest.App.Content.Language != "" { + app.Lang = openRTBRequest.App.Content.Language + } else { + app.Lang = "en" + } + } + app.Country = getCountryCode(openRTBRequest) + request.App = app + return nil +} + +// getClientTime: get field clientTime, format: 2006-01-02 15:04:05.000+0200 +func getClientTime(clientTime string) (newClientTime string) { + var zone = defaultTimeZone + t := time.Now().Local().Format(time.RFC822Z) + index := strings.IndexAny(t, "-+") + if index > 0 && len(t)-index == 5 { + zone = t[index:] + } + if clientTime == "" { + return time.Now().Format(timeFormat) + zone + } + if isMatched, _ := regexp.MatchString("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}[+-]{1}\\d{4}$", clientTime); isMatched { + return clientTime + } + if isMatched, _ := regexp.MatchString("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}$", clientTime); isMatched { + return clientTime + zone + } + return time.Now().Format(timeFormat) + zone +} + +// getHuaweiAdsReqDeviceInfo: get device information for HuaweiAds request +func getHuaweiAdsReqDeviceInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest, huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds) (err error) { + var device device + if openRTBRequest.Device != nil { + device.Type = int32(openRTBRequest.Device.DeviceType) + device.Useragent = openRTBRequest.Device.UA + device.Os = openRTBRequest.Device.OS + device.Version = openRTBRequest.Device.OSV + device.Maker = openRTBRequest.Device.Make + device.Model = openRTBRequest.Device.Model + if device.Model == "" { + device.Model = defaultModelName + } + device.Height = int32(openRTBRequest.Device.H) + device.Width = int32(openRTBRequest.Device.W) + device.Language = openRTBRequest.Device.Language + device.Pxratio = float32(openRTBRequest.Device.PxRatio) + var country = getCountryCode(openRTBRequest) + device.BelongCountry = country + device.LocaleCountry = country + device.Ip = openRTBRequest.Device.IP + } + // get oaid gaid imei in openRTBRequest.User.Ext.Data + if err = getDeviceID(&device, openRTBRequest); err != nil { + return err + } + request.Device = device + return nil +} + +func getCountryCode(openRTBRequest *openrtb2.BidRequest) string { + if openRTBRequest.Device != nil && openRTBRequest.Device.Geo != nil && openRTBRequest.Device.Geo.Country != "" { + return convertCountryCode(openRTBRequest.Device.Geo.Country) + } else if openRTBRequest.User != nil && openRTBRequest.User.Geo != nil && openRTBRequest.User.Geo.Country != "" { + return convertCountryCode(openRTBRequest.User.Geo.Country) + } else { + return defaultCountryName + } +} + +// convertCountryCode: ISO 3166-1 Alpha3 -> Alpha2, Some countries may use +func convertCountryCode(country string) (out string) { + if country == "" { + return defaultCountryName + } + var mapCountryCodeAlpha3ToAlpha2 = map[string]string{"CHL": "CL", "CHN": "CN", "ARE": "AE"} + if mappedCountry, exists := mapCountryCodeAlpha3ToAlpha2[country]; exists { + return mappedCountry + } + + if len(country) >= 3 { + return country[0:2] + } + + return defaultCountryName +} + +// getDeviceID include oaid gaid imei. In prebid mobile, use TargetingParams.addUserData("imei", "imei-test"); +func getDeviceID(device *device, openRTBRequest *openrtb2.BidRequest) (err error) { + if openRTBRequest.User == nil { + return errors.New("getDeviceID: openRTBRequest.User is nil.") + } + if openRTBRequest.User.Ext == nil { + return errors.New("getDeviceID: openRTBRequest.User.Ext is nil.") + } + var extUserDataHuaweiAds openrtb_ext.ExtUserDataHuaweiAds + if err := json.Unmarshal(openRTBRequest.User.Ext, &extUserDataHuaweiAds); err != nil { + return errors.New("Unmarshal: openRTBRequest.User.Ext -> extUserDataHuaweiAds failed") + } + var deviceId = extUserDataHuaweiAds.Data + if len(deviceId.Imei) == 0 && len(deviceId.Gaid) == 0 && len(deviceId.Oaid) == 0 { + return errors.New("getDeviceID: Imei ,Oaid, Gaid are all empty.") + } + if len(deviceId.Oaid) > 0 { + device.Oaid = deviceId.Oaid[0] + } + if len(deviceId.Gaid) > 0 { + device.Gaid = deviceId.Gaid[0] + } + if len(deviceId.Imei) > 0 { + device.Imei = deviceId.Imei[0] + } + if len(deviceId.ClientTime) > 0 { + device.ClientTime = getClientTime(deviceId.ClientTime[0]) + } + // IsTrackingEnabled = 1 - DNT + if openRTBRequest.Device != nil && openRTBRequest.Device.DNT != nil { + if device.Oaid != "" { + device.IsTrackingEnabled = strconv.Itoa(1 - int(*openRTBRequest.Device.DNT)) + } + if device.Gaid != "" { + device.GaidTrackingEnabled = strconv.Itoa(1 - int(*openRTBRequest.Device.DNT)) + } + } + return nil +} + +// getHuaweiAdsReqNetWorkInfo: for HuaweiAds request, include Carrier, Mcc, Mnc +func getHuaweiAdsReqNetWorkInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { + if openRTBRequest.Device != nil { + var network network + if openRTBRequest.Device.ConnectionType != nil { + network.Type = int32(*openRTBRequest.Device.ConnectionType) + } else { + network.Type = defaultUnknownNetworkType + } + + var cellInfos []cellInfo + if openRTBRequest.Device.MCCMNC != "" { + var arr = strings.Split(openRTBRequest.Device.MCCMNC, "-") + network.Carrier = 0 + if len(arr) >= 2 { + cellInfos = append(cellInfos, cellInfo{ + Mcc: arr[0], + Mnc: arr[1], + }) + var str = arr[0] + arr[1] + if str == "46000" || str == "46002" || str == "46007" { + network.Carrier = 2 + } else if str == "46001" || str == "46006" { + network.Carrier = 1 + } else if str == "46003" || str == "46005" || str == "46011" { + network.Carrier = 3 + } else { + network.Carrier = 99 + } + } + } + network.CellInfo = cellInfos + request.Network = network + } +} + +// getHuaweiAdsReqRegsInfo: get regs information for HuaweiAds request, include Coppa +func getHuaweiAdsReqRegsInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { + if openRTBRequest.Regs != nil && openRTBRequest.Regs.COPPA >= 0 { + var regs regs + regs.Coppa = int32(openRTBRequest.Regs.COPPA) + request.Regs = regs + } +} + +// getHuaweiAdsReqGeoInfo: get geo information for HuaweiAds request, include Lon, Lat, Accuracy, Lastfix +func getHuaweiAdsReqGeoInfo(request *huaweiAdsRequest, openRTBRequest *openrtb2.BidRequest) { + if openRTBRequest.Device != nil && openRTBRequest.Device.Geo != nil { + var geo geo + geo.Lon = float32(openRTBRequest.Device.Geo.Lon) + geo.Lat = float32(openRTBRequest.Device.Geo.Lat) + geo.Accuracy = int32(openRTBRequest.Device.Geo.Accuracy) + geo.Lastfix = int32(openRTBRequest.Device.Geo.LastFix) + request.Geo = geo + } +} + +func unmarshalExtImpHuaweiAds(openRTBImp *openrtb2.Imp) (*openrtb_ext.ExtImpHuaweiAds, error) { + var bidderExt adapters.ExtImpBidder + var huaweiAdsImpExt openrtb_ext.ExtImpHuaweiAds + if err := json.Unmarshal(openRTBImp.Ext, &bidderExt); err != nil { + return nil, errors.New("Unmarshal: openRTBImp.Ext -> bidderExt failed") + } + if err := json.Unmarshal(bidderExt.Bidder, &huaweiAdsImpExt); err != nil { + return nil, errors.New("Unmarshal: bidderExt.Bidder -> huaweiAdsImpExt failed") + } + if huaweiAdsImpExt.SlotId == "" { + return nil, errors.New("ExtImpHuaweiAds: slotid is empty.") + } + if huaweiAdsImpExt.Adtype == "" { + return nil, errors.New("ExtImpHuaweiAds: adtype is empty.") + } + if huaweiAdsImpExt.PublisherId == "" { + return nil, errors.New("ExtHuaweiAds: publisherid is empty.") + } + if huaweiAdsImpExt.SignKey == "" { + return nil, errors.New("ExtHuaweiAds: signkey is empty.") + } + if huaweiAdsImpExt.KeyId == "" { + return nil, errors.New("ExtImpHuaweiAds: keyid is empty.") + } + return &huaweiAdsImpExt, nil +} + +func checkRespStatusCode(response *adapters.ResponseData) error { + if response.StatusCode == http.StatusNoContent { + return nil + } + + if response.StatusCode == http.StatusServiceUnavailable { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), + } + } + + if response.StatusCode != http.StatusOK { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: [ %d ]. Run with request.debug = 1 for more info", response.StatusCode), + } + } + + if response.Body == nil { + return errors.New("bidderRawResponse body is empty") + } + return nil +} + +func checkHuaweiAdsResponseRetcode(response huaweiAdsResponse) error { + if response.Retcode == 200 || response.Retcode == 204 || response.Retcode == 206 { + return nil + } + + if (response.Retcode < 600 && response.Retcode >= 400) || (response.Retcode < 300 && response.Retcode > 200) { + return &errortypes.BadInput{ + Message: fmt.Sprintf("HuaweiAdsResponse retcode: %d , reason: %s", response.Retcode, response.Reason), + } + } + return nil +} + +// convertHuaweiAdsResp2BidderResp: convert HuaweiAds' response into bidder's response +func (a *adapter) convertHuaweiAdsResp2BidderResp(huaweiAdsResponse *huaweiAdsResponse, openRTBRequest *openrtb2.BidRequest) (bidderResponse *adapters.BidderResponse, err error) { + if len(huaweiAdsResponse.Multiad) == 0 { + return nil, errors.New("convertHuaweiAdsResp2BidderResp: multiad length is 0, get no ads from huawei side.") + } + bidderResponse = adapters.NewBidderResponseWithBidsCapacity(len(huaweiAdsResponse.Multiad)) + // Default Currency: CNY + bidderResponse.Currency = "CNY" + + // record request Imp (slotid->imp, slotid->openrtb_ext.bidtype) + mapSlotid2Imp := make(map[string]openrtb2.Imp, len(openRTBRequest.Imp)) + mapSlotid2MediaType := make(map[string]openrtb_ext.BidType, len(openRTBRequest.Imp)) + for _, imp := range openRTBRequest.Imp { + huaweiAdsExt, err := unmarshalExtImpHuaweiAds(&imp) + if err != nil { + continue + } + mapSlotid2Imp[huaweiAdsExt.SlotId] = imp + + var mediaType = openrtb_ext.BidTypeBanner + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } else if imp.Native != nil { + mediaType = openrtb_ext.BidTypeNative + } else if imp.Audio != nil { + mediaType = openrtb_ext.BidTypeAudio + } + mapSlotid2MediaType[huaweiAdsExt.SlotId] = mediaType + } + + if len(mapSlotid2MediaType) < 1 || len(mapSlotid2Imp) < 1 { + return nil, errors.New("convertHuaweiAdsResp2BidderResp: openRTBRequest.imp is nil") + } + + for _, ad30 := range huaweiAdsResponse.Multiad { + if mapSlotid2Imp[ad30.Slotid].ID == "" { + continue + } + + if ad30.Retcode30 != 200 { + continue + } + + for _, content := range ad30.Content { + var bid openrtb2.Bid + bid.ID = mapSlotid2Imp[ad30.Slotid].ID + bid.ImpID = mapSlotid2Imp[ad30.Slotid].ID + // The bidder has already helped us automatically convert the currency price, here only the CNY price is filled in + bid.Price = content.Price + bid.CrID = content.Contentid + // All currencies should be the same + if content.Cur != "" { + bidderResponse.Currency = content.Cur + } + + bid.AdM, bid.W, bid.H, err = a.handleHuaweiAdsContent(ad30.AdType, &content, mapSlotid2MediaType[ad30.Slotid], mapSlotid2Imp[ad30.Slotid]) + if err != nil { + return nil, err + } + bid.ADomain = append(bid.ADomain, "huaweiads") + bid.NURL = getNurl(content) + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: mapSlotid2MediaType[ad30.Slotid], + }) + } + } + return bidderResponse, nil +} + +func getNurl(content content) string { + if len(content.Monitor) == 0 { + return "" + } + for _, monitor := range content.Monitor { + if monitor.EventType == "win" && len(monitor.Url) != 0 { + return monitor.Url[0] + } + } + return "" +} + +// handleHuaweiAdsContent: get field Adm, Width, Height +func (a *adapter) handleHuaweiAdsContent(adType int32, content *content, bidType openrtb_ext.BidType, imp openrtb2.Imp) ( + adm string, adWidth int64, adHeight int64, err error) { + // v1: only support banner, native + switch bidType { + case openrtb_ext.BidTypeBanner: + adm, adWidth, adHeight, err = a.extractAdmBanner(adType, content, bidType, imp) + case openrtb_ext.BidTypeNative: + adm, adWidth, adHeight, err = a.extractAdmNative(adType, content, bidType, imp) + case openrtb_ext.BidTypeVideo: + adm, adWidth, adHeight, err = a.extractAdmVideo(adType, content, bidType, imp) + default: + return "", 0, 0, errors.New("no support bidtype: audio") + } + + if err != nil { + return "", 0, 0, fmt.Errorf("getAdmFromHuaweiAdsContent failed: %s", err) + } + return adm, adWidth, adHeight, nil +} + +// extractAdmBanner: banner ad +func (a *adapter) extractAdmBanner(adType int32, content *content, bidType openrtb_ext.BidType, imp openrtb2.Imp) (adm string, + adWidth int64, adHeight int64, err error) { + if adType != banner { + return "", 0, 0, errors.New("extractAdmBanner: huaweiads response is not a banner ad") + } + var creativeType = content.Creativetype + if content.Creativetype > 100 { + creativeType = creativeType - 100 + } + if creativeType == text || creativeType == bigPicture || creativeType == bigPicture2 || + creativeType == smallPicture || creativeType == threeSmallPicturesText || + creativeType == iconText || creativeType == gif { + return a.extractAdmPicture(content) + } else if creativeType == videoText || creativeType == video || creativeType == videoWithPicturesText { + return a.extractAdmVideo(adType, content, bidType, imp) + } else { + return "", 0, 0, errors.New("no banner support creativetype") + } +} + +// extractAdmNative: native ad +func (a *adapter) extractAdmNative(adType int32, content *content, bidType openrtb_ext.BidType, openrtb2Imp openrtb2.Imp) (adm string, + adWidth int64, adHeight int64, err error) { + if adType != native { + return "", 0, 0, errors.New("extractAdmNative: response is not a native ad") + } + if openrtb2Imp.Native == nil { + return "", 0, 0, errors.New("extractAdmNative: imp.Native is nil") + } + if openrtb2Imp.Native.Request == "" { + return "", 0, 0, errors.New("extractAdmNative: imp.Native.Request is empty") + } + + var nativePayload nativeRequests.Request + if err := json.Unmarshal(json.RawMessage(openrtb2Imp.Native.Request), &nativePayload); err != nil { + return "", 0, 0, err + } + + var nativeResult nativeResponse.Response + var linkObject nativeResponse.Link + if content.MetaData.ClickUrl != "" { + linkObject.URL = content.MetaData.ClickUrl + } else if content.MetaData.Intent != "" { + linkObject.URL = getDecodeValue(content.MetaData.Intent) + } + + nativeResult.Assets = make([]nativeResponse.Asset, 0, len(nativePayload.Assets)) + var imgIndex = 0 + var iconIndex = 0 + for _, asset := range nativePayload.Assets { + var responseAsset nativeResponse.Asset + if asset.Title != nil { + var titleObject nativeResponse.Title + titleObject.Text = getDecodeValue(content.MetaData.Title) + titleObject.Len = int64(len(titleObject.Text)) + responseAsset.Title = &titleObject + } else if asset.Video != nil { + var videoObject nativeResponse.Video + var err error + if videoObject.VASTTag, adWidth, adHeight, err = a.extractAdmVideo(adType, content, bidType, openrtb2Imp); err != nil { + return "", 0, 0, err + } + responseAsset.Video = &videoObject + } else if asset.Img != nil { + var imgObject nativeResponse.Image + imgObject.URL = "" + imgObject.Type = asset.Img.Type + if asset.Img.Type == native1.ImageAssetTypeIcon { + if len(content.MetaData.Icon) > iconIndex { + imgObject.URL = content.MetaData.Icon[iconIndex].Url + imgObject.W = content.MetaData.Icon[iconIndex].Width + imgObject.H = content.MetaData.Icon[iconIndex].Height + iconIndex++ + } + } else { + if len(content.MetaData.ImageInfo) > imgIndex { + imgObject.URL = content.MetaData.ImageInfo[imgIndex].Url + imgObject.W = content.MetaData.ImageInfo[imgIndex].Width + imgObject.H = content.MetaData.ImageInfo[imgIndex].Height + imgIndex++ + } + } + if adHeight == 0 && adWidth == 0 { + adHeight = imgObject.H + adWidth = imgObject.W + } + responseAsset.Img = &imgObject + } else if asset.Data != nil { + var dataObject nativeResponse.Data + dataObject.Label = "" + dataObject.Value = "" + if asset.Data.Type == native1.DataAssetTypeDesc || asset.Data.Type == native1.DataAssetTypeDesc2 { + dataObject.Label = "desc" + dataObject.Value = getDecodeValue(content.MetaData.Description) + } + responseAsset.Data = &dataObject + } + var id = asset.ID + responseAsset.ID = &id + nativeResult.Assets = append(nativeResult.Assets, responseAsset) + } + + // dsp imp click tracking + imp click tracking + if content.Monitor != nil { + for _, monitor := range content.Monitor { + if len(monitor.Url) == 0 { + continue + } + if monitor.EventType == "click" { + linkObject.ClickTrackers = append(linkObject.ClickTrackers, monitor.Url...) + } + if monitor.EventType == "imp" { + nativeResult.ImpTrackers = append(nativeResult.ImpTrackers, monitor.Url...) + } + } + } + nativeResult.Link = linkObject + nativeResult.Ver = "1.1" + if nativePayload.Ver != "" { + nativeResult.Ver = nativePayload.Ver + } + + var result []byte + if result, err = jsonEncode(nativeResult); err != nil { + return "", 0, 0, err + } + return strings.Replace(string(result), "\n", "", -1), adWidth, adHeight, nil +} + +func getDecodeValue(str string) string { + if str == "" { + return "" + } + if decodeValue, err := url.QueryUnescape(str); err == nil { + return decodeValue + } else { + return "" + } +} + +func jsonEncode(nativeResult nativeResponse.Response) ([]byte, error) { + buffer := &bytes.Buffer{} + encoder := json.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + err := encoder.Encode(nativeResult) + return buffer.Bytes(), err +} + +// extractAdmPicture: For banner single picture +func (a *adapter) extractAdmPicture(content *content) (adm string, adWidth int64, adHeight int64, err error) { + if content == nil { + return "", 0, 0, errors.New("extractAdmPicture: content is empty") + } + + var clickUrl = "" + if content.MetaData.ClickUrl != "" { + clickUrl = content.MetaData.ClickUrl + } else if content.MetaData.Intent != "" { + clickUrl = getDecodeValue(content.MetaData.Intent) + } + + var imageInfoUrl string + if content.MetaData.ImageInfo != nil { + imageInfoUrl = content.MetaData.ImageInfo[0].Url + adHeight = content.MetaData.ImageInfo[0].Height + adWidth = content.MetaData.ImageInfo[0].Width + } else { + return "", 0, 0, errors.New("content.MetaData.ImageInfo is empty") + } + + var imageTitle = "" + imageTitle = getDecodeValue(content.MetaData.Title) + // dspImp, Imp, dspClick, Click tracking all can be found in MonitorUrl(imp ,click) + dspImpTrackings, dspClickTrackings := getDspImpClickTrackings(content) + var dspImpTrackings2StrImg strings.Builder + for i := 0; i < len(dspImpTrackings); i++ { + dspImpTrackings2StrImg.WriteString(` `) + } + + adm = " " + + `` + imageTitle + " " + + " " + + " " + + " " + + dspImpTrackings2StrImg.String() + + `" + return adm, adWidth, adHeight, nil +} + +func getDspImpClickTrackings(content *content) (dspImpTrackings []string, dspClickTrackings string) { + for _, monitor := range content.Monitor { + if len(monitor.Url) != 0 { + switch monitor.EventType { + case "imp": + dspImpTrackings = monitor.Url + case "click": + dspClickTrackings = getStrings(monitor.Url) + } + } + } + return dspImpTrackings, dspClickTrackings +} + +func getStrings(eles []string) string { + if len(eles) == 0 { + return "" + } + var strs strings.Builder + for i := 0; i < len(eles); i++ { + strs.WriteString("\"" + eles[i] + "\"") + if i < len(eles)-1 { + strs.WriteString(",") + } + } + return strs.String() +} + +// getDuration: millisecond -> format: 00:00:00.000 +func getDuration(duration int64) string { + var dur time.Duration = time.Duration(duration) * time.Millisecond + t := time.Time{}.Add(dur) + return t.Format("15:04:05.000") +} + +// extractAdmVideo: get field adm for video, vast 3.0 +func (a *adapter) extractAdmVideo(adType int32, content *content, bidType openrtb_ext.BidType, opentrb2Imp openrtb2.Imp) (adm string, + adWidth int64, adHeight int64, err error) { + if content == nil { + return "", 0, 0, errors.New("extractAdmVideo: content is empty") + } + + var clickUrl = "" + if content.MetaData.ClickUrl != "" { + clickUrl = content.MetaData.ClickUrl + } else if content.MetaData.Intent != "" { + clickUrl = getDecodeValue(content.MetaData.Intent) + } + + var mime = "video/mp4" + var resourceUrl = "" + var duration = "" + if adType == roll { + // roll ad get information from mediafile + if content.MetaData.MediaFile.Mime != "" { + mime = content.MetaData.MediaFile.Mime + } + adWidth = content.MetaData.MediaFile.Width + adHeight = content.MetaData.MediaFile.Height + if content.MetaData.MediaFile.Url != "" { + resourceUrl = content.MetaData.MediaFile.Url + } else { + return "", 0, 0, errors.New("extractAdmVideo: Content.MetaData.MediaFile.Url is empty") + } + duration = getDuration(content.MetaData.Duration) + } else { + if content.MetaData.VideoInfo.VideoDownloadUrl != "" { + resourceUrl = content.MetaData.VideoInfo.VideoDownloadUrl + } else { + return "", 0, 0, errors.New("extractAdmVideo: content.MetaData.VideoInfo.VideoDownloadUrl is empty") + } + if content.MetaData.VideoInfo.Width != 0 && content.MetaData.VideoInfo.Height != 0 { + adWidth = int64(content.MetaData.VideoInfo.Width) + adHeight = int64(content.MetaData.VideoInfo.Height) + } else if bidType == openrtb_ext.BidTypeVideo { + if opentrb2Imp.Video != nil && opentrb2Imp.Video.W != 0 && opentrb2Imp.Video.H != 0 { + adWidth = opentrb2Imp.Video.W + adHeight = opentrb2Imp.Video.H + } + } else { + return "", 0, 0, errors.New("extractAdmVideo: cannot get width, height") + } + duration = getDuration(int64(content.MetaData.VideoInfo.VideoDuration)) + } + + var adTitle = getDecodeValue(content.MetaData.Title) + var adId = content.Contentid + var creativeId = content.Contentid + var trackingEvents strings.Builder + var dspImpTracking2Str = "" + var dspClickTracking2Str = "" + var errorTracking2Str = "" + for _, monitor := range content.Monitor { + if len(monitor.Url) == 0 { + continue + } + var event = "" + switch monitor.EventType { + case "vastError": + errorTracking2Str = getVastImpClickErrorTrackingUrls(monitor.Url, "vastError") + case "imp": + dspImpTracking2Str = getVastImpClickErrorTrackingUrls(monitor.Url, "imp") + case "click": + dspClickTracking2Str = getVastImpClickErrorTrackingUrls(monitor.Url, "click") + case "userclose": + event = "skip&closeLinear" + case "playStart": + event = "start" + case "playEnd": + event = "complete" + case "playResume": + event = "resume" + case "playPause": + event = "pause" + case "soundClickOff": + event = "mute" + case "soundClickOn": + event = "unmute" + default: + } + if event != "" { + if event != "skip&closeLinear" { + trackingEvents.WriteString(getVastEventTrackingUrls(monitor.Url, event)) + } else { + trackingEvents.WriteString(getVastEventTrackingUrls(monitor.Url, "skip&closeLinear")) + } + } + } + + adm = `` + + `` + + `` + + "HuaweiAds" + + "" + adTitle + "" + + errorTracking2Str + dspImpTracking2Str + + "" + + `` + + "" + + "" + duration + "" + + "" + trackingEvents.String() + "" + + "" + + "" + + dspClickTracking2Str + + "" + + "" + + ` ` + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + return adm, adWidth, adHeight, nil +} + +func getVastImpClickErrorTrackingUrls(urls []string, eventType string) (result string) { + var trackingUrls strings.Builder + for _, url := range urls { + if eventType == "click" { + trackingUrls.WriteString("") + } else if eventType == "imp" { + trackingUrls.WriteString("") + } else if eventType == "vastError" { + trackingUrls.WriteString("") + } + } + return trackingUrls.String() +} + +func getVastEventTrackingUrls(urls []string, eventType string) (result string) { + var trackingUrls strings.Builder + for _, eventUrl := range urls { + if eventType == "skip&closeLinear" { + trackingUrls.WriteString(`") + } else { + trackingUrls.WriteString(`") + } + } + return trackingUrls.String() +} + +func computeHmacSha256(message string, signKey string) string { + h := hmac.New(sha256.New, []byte(signKey)) + h.Write([]byte(message)) + return hex.EncodeToString(h.Sum(nil)) +} + +// getDigestAuthorization: get digest authorization for request header +func getDigestAuthorization(huaweiAdsImpExt *openrtb_ext.ExtImpHuaweiAds, isTestAuthorization bool) string { + var nonce = strconv.FormatInt(time.Now().UnixNano()/1e6, 10) + // this is for test case, time 2021/8/20 19:30 + if isTestAuthorization { + nonce = "1629473330823" + } + var apiKey = huaweiAdsImpExt.PublisherId + ":ppsadx/getResult:" + huaweiAdsImpExt.SignKey + return "Digest username=" + huaweiAdsImpExt.PublisherId + "," + + "realm=ppsadx/getResult," + + "nonce=" + nonce + "," + + "response=" + computeHmacSha256(nonce+":POST:/ppsadx/getResult", apiKey) + "," + + "algorithm=HmacSHA256,usertype=1,keyid=" + huaweiAdsImpExt.KeyId +} diff --git a/adapters/huaweiads/huaweiads_test.go b/adapters/huaweiads/huaweiads_test.go new file mode 100644 index 00000000000..03b25d174b0 --- /dev/null +++ b/adapters/huaweiads/huaweiads_test.go @@ -0,0 +1,18 @@ +package huaweiads + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderHuaweiAds, config.Adapter{ + Endpoint: "https://huaweiads.com/adxtest/"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + adapterstest.RunJSONBidderTest(t, "huaweiadstest", bidder) +} diff --git a/adapters/huaweiads/huaweiadstest/exemplary/banner1.json b/adapters/huaweiads/huaweiadstest/exemplary/banner1.json new file mode 100644 index 00000000000..866ff173ee8 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/banner1.json @@ -0,0 +1,254 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301", + "content": { + "language": "zh" + } + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "CHN" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "regs": { + "coppa": 1 + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "zh", + "country": "CN", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "CN", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "CN" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + "coppa": 1 + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 61, + "dspcost": 108, + "multiad": [ + { + "adtype": 8, + "brsetting": "Y", + "content": [ + { + "clickActionList": [ + 1 + ], + "contentid": "58025103", + "creativetype": 2, + "ctrlSwitchs": "001011001001010112", + "endtime": 1621428898335, + "filterList": [ + 3 + ], + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "imageInfo": [ + { + "checkSha256Flag": 1, + "height": 300, + "imageType": "img", + "sha256": "f5d8487cddaecec45b73fc078649478c80ec646bfec8ed7da4ff931f90eab232", + "url": "https://ads.huawei.com/usermgtportal/home/img/huawei_logo_black.aaec817d.svg", + "width": 250 + } + ], + "label": "Banner_API", + "landingPageType": "3" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click1", + "http://test/click2", + "http://test/click3" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp1", + "http://test/imp2", + "http://test/imp3" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "m8x9x3rzff" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 8, + "eventTypeList": [ + "exception_6" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": " ", + "adomain": [ + "huaweiads" + ], + "crid": "58025103", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8, + "h": 300, + "w": 250 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/banner2.json b/adapters/huaweiads/huaweiadstest/exemplary/banner2.json new file mode 100644 index 00000000000..ab3dea21f04 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/banner2.json @@ -0,0 +1,271 @@ +{ + "mockBidRequest": { + "id": "a3b18c5b-6708-4bb7-b41b-40ed37d89561", + "source": { + "tid": "a3b18c5b-6708-4bb7-b41b-40ed37d89561" + }, + "imp": [ + { + "id": "PrebidMobile", + "secure": 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [ + 5 + ] + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "1001-1" + } + }, + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "device": { + "make": "HUAWEI", + "model": "LYA-AL00", + "ua": "ua", + "lmt": 0, + "os": "android", + "osv": "29", + "language": "zh", + "w": 360, + "h": 755, + "pxratio": 3, + "mccmnc": "424-3", + "carrier": "carrier", + "connectiontype": 1, + "geo": { + "country": "ZAF" + } + }, + "app": { + "bundle": "org.prebid.mobile.prebidjavademo", + "ver": "1.11", + "name": "API1.0Demo", + "publisher": { + "id": "1001" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.11" + } + } + }, + "user": { + "gender": "O", + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "1001" + }, + "cache": { + "bids": {} + }, + "targeting": {} + } + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "ua" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "API1.0Demo", + "pkgname": "org.prebid.mobile.prebidjavademo", + "version": "1.11" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "device": { + "height": 755, + "language": "zh", + "oaid": "oaid", + "os": "android", + "localeCountry": "ZA", + "pxratio": 3, + "width": 360, + "model": "LYA-AL00", + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "ua", + "version": "29", + "maker": "HUAWEI", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "carrier": 99, + "cellInfo": [ + { + "mcc": "424", + "mnc": "3" + } + ], + "type": 1 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 61, + "dspcost": 108, + "multiad": [ + { + "adtype": 8, + "brsetting": "Y", + "content": [ + { + "clickActionList": [ + 1 + ], + "contentid": "58025103", + "creativetype": 2, + "endtime": 1621428898335, + "filterList": [ + 3 + ], + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "imageInfo": [ + { + "checkSha256Flag": 1, + "height": 250, + "imageType": "img", + "sha256": "", + "url": "https://ads.huawei.com/usermgtportal/home/img/huawei_logo_black.aaec817d.svg", + "width": 300 + } + ], + "label": "Banner_API", + "landingPageType": "3" + }, + "monitor": [ + { + "eventType": "click" + }, + { + "eventType": "imp", + "url": [ + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "m8x9x3rzff" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 8, + "eventTypeList": [ + "exception_6" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": " ", + "adomain": [ + "huaweiads" + ], + "h": 250, + "w": 300, + "crid": "58025103", + "id": "PrebidMobile", + "impid": "PrebidMobile", + "price": 2.8 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json new file mode 100644 index 00000000000..d804375c997 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json @@ -0,0 +1,326 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":107,\"video\":{\"mimes\":[\"mp4\"],\"minduration\":100,\"maxduration\":100,\"protocols\":[1,2]},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "u42ohmaufh", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.huawei.browser", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "152.193.6.74", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token", + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.huawei.browser", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 3, + "slotid": "u42ohmaufh", + "detailedCreativeTypeList": [ + "903" + ], + "h": 200, + "test": 0, + "w": 200 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "152.193.6.74", + "localeCountry": "ZA", + "pxratio": 23.01, + "model": "COL-TEST", + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "multiad": [ + { + "adtype": 3, + "content": [ + { + "contentid": "58022259", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 2, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "apkInfo": { + "appIcon": "https://pps-icon.png", + "appName": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "fileSize": 118902470, + "packageName": "com.demo.package", + "permPromptForCard": "0", + "popNotify": 1, + "popUpAfterInstallNew": 1, + "priorInstallWay": "2", + "sha256": "sha256", + "url": "https://test/apkurl", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "appId": "101219405", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "cta": "%e5%ae%89%e8%a3%85", + "duration": 6038, + "icon": [ + { + "checkSha256Flag": 1, + "fileSize": 10797, + "height": 160, + "imageType": "img", + "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", + "url": "https://pps-icon.png", + "width": 160 + } + ], + "imageInfo": [ + { + "checkSha256Flag": 0, + "height": 350, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image1.jpg", + "width": 400 + }, + { + "checkSha256Flag": 0, + "height": 300, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image2.jpg", + "width": 400 + } + ], + "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "landingPageType": "3", + "marketAppId": "C101219405", + "title": "%2Ftest%2F", + "description": "this is a test ad", + "videoInfo": { + "autoPlayAreaRatio": 100, + "autoStopPlayAreaRatio": 10, + "checkSha256Flag": 1, + "sha256": "aa08c8ffce82bbcd37cabefd6c8972b407de48f0b4e332e06d4cc18d25377d77", + "timeBeforeVideoAutoPlay": 50, + "videoAutoPlayOnWifi": "y", + "videoAutoPlayWithSound": "n", + "videoDownloadUrl": "https://video.mp4", + "videoDuration": 6038, + "videoFileSize": 949951, + "videoPlayMode": 2, + "videoRatio": 0.5625, + "width": 600, + "height": 500 + } + }, + "monitor": [ + { + "eventType": "vastError", + "url": [ + "http://test/vastError" + ] + }, + { + "eventType": "click", + "url": [ + "http://test/click", + "http://test/dspclick" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp", + "http://test/dspimp" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + }, + { + "eventType": "playStart", + "url": [ + "http://test/playStart" + ] + }, + { + "eventType": "playEnd", + "url": [ + "http://test/playEnd1", + "http://test/playEnd2" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "u42ohmaufh" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 3, + "eventTypeList": [ + "installFail" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adomain": [ + "huaweiads" + ], + "crid": "58022259", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":107,\"video\":{\"vasttag\":\"HuaweiAds/test/00:00:06.038 \"}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\",\"http://test/dspclick\"]},\"imptrackers\":[\"http://test/imp\",\"http://test/dspimp\"]}", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8, + "h": 500, + "w": 600 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json new file mode 100644 index 00000000000..62e2dcd6e10 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json @@ -0,0 +1,315 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "u42ohmaufh", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.huawei.browser", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "dnt": 0, + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.huawei.browser", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 3, + "slotid": "u42ohmaufh", + "detailedCreativeTypeList": [ + "909" + ], + "h": 200, + "test": 0, + "w": 200 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "model": "COL-TEST", + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "gaidTrackingEnabled": "1", + "isTrackingEnabled": "1", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 28, + "dspcost": 59, + "multiad": [ + { + "adtype": 3, + "content": [ + { + "contentid": "58022259", + "creativetype": 106, + "ctrlSwitchs": "001011101001010212", + "endtime": 1621344684645, + "interactiontype": 2, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "apkInfo": { + "appDesc": "43+%E4%BA%BF%E6%AC%A1%E5%AE%89%E8%A3%85", + "appIcon": "https://icon.png", + "appName": "demo", + "fileSize": 118902470, + "packageName": "com.demo.package", + "permPromptForCard": "0", + "popNotify": 1, + "popUpAfterInstallNew": 1, + "priorInstallWay": "2", + "sha256": "sha256", + "url": "http://test/url", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "appId": "101219405", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "duration": 6038, + "description": "", + "icon": [ + { + "checkSha256Flag": 1, + "fileSize": 10797, + "height": 160, + "imageType": "img", + "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", + "url": "https://pps-icon.png", + "width": 160 + } + ], + "imageInfo": [ + { + "checkSha256Flag": 0, + "height": 1280, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image.jpg", + "width": 720 + } + ], + "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "landingPageType": "3", + "marketAppId": "C101219405", + "title": "%2Ftest%2F" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "download", + "url": [ + "http://test/download" + ] + }, + { + "eventType": "install", + "url": [ + "http://test/install" + ] + }, + { + "eventType": "downloadstart", + "url": [ + "http://test/downloadstart" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + }, + { + "eventType": "playStart", + "url": [ + "http://test/playStart" + ] + }, + { + "eventType": "playEnd", + "url": [ + "http://test/playEnd" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "u42ohmaufh" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 3, + "eventTypeList": [ + "installFail" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adomain": [ + "huaweiads" + ], + "crid": "58022259", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":103,\"img\":{\"type\":3,\"url\":\"http://image.jpg\",\"w\":720,\"h\":1280}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"imptrackers\":[\"http://test/imp\"]}", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8, + "h": 1280, + "w": 720 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json new file mode 100644 index 00000000000..a68d8b8af36 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json @@ -0,0 +1,334 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"w\":200,\"h\":200},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "u42ohmaufh", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.huawei.browser", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "152.193.6.74", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.huawei.browser", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 3, + "slotid": "u42ohmaufh", + "detailedCreativeTypeList": [ + "904" + ], + "h": 200, + "test": 0, + "w": 200 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "152.193.6.74", + "localeCountry": "ZA", + "pxratio": 23.01, + "model": "COL-TEST", + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "multiad": [ + { + "adtype": 3, + "content": [ + { + "contentid": "58022259", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 2, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "apkInfo": { + "appIcon": "https://icon.png", + "appName": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "fileSize": 118902470, + "packageName": "com.demo.package", + "permPromptForCard": "0", + "popNotify": 1, + "popUpAfterInstallNew": 1, + "priorInstallWay": "2", + "sha256": "sha256", + "url": "https://test/apkurl", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "appId": "101219405", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "cta": "%e5%ae%89%e8%a3%85", + "duration": 6038, + "icon": [ + { + "checkSha256Flag": 1, + "fileSize": 10797, + "height": 160, + "imageType": "img", + "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", + "url": "https://icon1.png", + "width": 160 + }, + { + "checkSha256Flag": 1, + "fileSize": 10797, + "height": 320, + "imageType": "img", + "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", + "url": "https://icon2.png", + "width": 160 + } + ], + "imageInfo": [ + { + "checkSha256Flag": 0, + "height": 350, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image1.jpg", + "width": 400 + }, + { + "checkSha256Flag": 0, + "height": 300, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image2.jpg", + "width": 400 + }, + { + "checkSha256Flag": 0, + "height": 300, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image3.jpg", + "width": 400 + } + ], + "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "landingPageType": "3", + "marketAppId": "C101219405", + "title": "%2Ftest%2F", + "description": "this is a test ad" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "download", + "url": [ + "http://test/download" + ] + }, + { + "eventType": "install", + "url": [ + "http://test/install" + ] + }, + { + "eventType": "downloadstart", + "url": [ + "http://test/downloadstart" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + }, + { + "eventType": "playStart", + "url": [ + "http://test/playStart" + ] + }, + { + "eventType": "playEnd", + "url": [ + "http://test/playEnd" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "u42ohmaufh" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 3, + "eventTypeList": [ + "installFail" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adomain": [ + "huaweiads" + ], + "crid": "58022259", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":102,\"img\":{\"type\":3,\"url\":\"http://image2.jpg\",\"w\":400,\"h\":300}},{\"id\":103,\"img\":{\"type\":3,\"url\":\"http://image3.jpg\",\"w\":400,\"h\":300}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"imptrackers\":[\"http://test/imp\"]}", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8, + "h": 350, + "w": 400 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json new file mode 100644 index 00000000000..a637203002a --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json @@ -0,0 +1,334 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":102,\"img\":{\"type\":1,\"w\":20,\"h\":20},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "u42ohmaufh", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.huawei.browser", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "152.193.6.74", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.huawei.browser", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 3, + "slotid": "u42ohmaufh", + "detailedCreativeTypeList": [ + "904" + ], + "h": 200, + "test": 0, + "w": 200 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "152.193.6.74", + "localeCountry": "ZA", + "pxratio": 23.01, + "model": "COL-TEST", + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "multiad": [ + { + "adtype": 3, + "content": [ + { + "contentid": "58022259", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 2, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "apkInfo": { + "appIcon": "https://icon.png", + "appName": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "fileSize": 118902470, + "packageName": "com.demo.package", + "permPromptForCard": "0", + "popNotify": 1, + "popUpAfterInstallNew": 1, + "priorInstallWay": "2", + "sha256": "sha256", + "url": "https://test/apkurl", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "appId": "101219405", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "cta": "%e5%ae%89%e8%a3%85", + "duration": 6038, + "icon": [ + { + "checkSha256Flag": 1, + "fileSize": 10797, + "height": 160, + "imageType": "img", + "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", + "url": "https://icon1.png", + "width": 160 + }, + { + "checkSha256Flag": 1, + "fileSize": 10797, + "height": 320, + "imageType": "img", + "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", + "url": "https://icon2.png", + "width": 160 + } + ], + "imageInfo": [ + { + "checkSha256Flag": 0, + "height": 350, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image1.jpg", + "width": 400 + }, + { + "checkSha256Flag": 0, + "height": 300, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image2.jpg", + "width": 400 + }, + { + "checkSha256Flag": 0, + "height": 300, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image3.jpg", + "width": 400 + } + ], + "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "landingPageType": "3", + "marketAppId": "C101219405", + "title": "%2Ftest%2F", + "description": "this is a test ad" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "download", + "url": [ + "http://test/download" + ] + }, + { + "eventType": "install", + "url": [ + "http://test/install" + ] + }, + { + "eventType": "downloadstart", + "url": [ + "http://test/downloadstart" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + }, + { + "eventType": "playStart", + "url": [ + "http://test/playStart" + ] + }, + { + "eventType": "playEnd", + "url": [ + "http://test/playEnd" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "u42ohmaufh" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 3, + "eventTypeList": [ + "installFail" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adomain": [ + "huaweiads" + ], + "crid": "58022259", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":101,\"img\":{\"type\":3,\"url\":\"http://image1.jpg\",\"w\":400,\"h\":350}},{\"id\":102,\"img\":{\"type\":1,\"url\":\"https://icon1.png\",\"w\":160,\"h\":160}},{\"id\":103,\"img\":{\"type\":3,\"url\":\"http://image2.jpg\",\"w\":400,\"h\":300}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"this is a test ad\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"imptrackers\":[\"http://test/imp\"]}", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8, + "h": 350, + "w": 400 + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/exemplary/video.json b/adapters/huaweiads/huaweiadstest/exemplary/video.json new file mode 100644 index 00000000000..ec589d3d714 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/exemplary/video.json @@ -0,0 +1,338 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video\/mp4" + ], + "playbackmethod": [ + 2 + ], + "protocols": [ + 2 + ], + "w": 300, + "h": 250, + "placement": 2, + "linearity": 1 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3roll", + "adtype": "roll", + "publisherid": "123123123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-08-10 20:01:11.214+0200" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123123123,realm=ppsadx/getResult,nonce=1629473330823,response=6fdc975d3adac426cbe607eec736f40ad3db8413312457431e391580e1b475c4,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 60, + "slotid": "m8x9x3roll", + "test": 0 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "model": "COL-TEST", + "clientTime": "2018-08-10 20:01:11.214+0200", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 60, + "brsetting": "Y", + "content": [ + { + "contentid": "58001445", + "creativetype": 106, + "endtime": 1621344684645, + "interactiontype": 2, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "mediaFile": { + "mime": "video/mp4", + "width": 720, + "height": 1280, + "fileSize": 10000, + "url": "https://test.png", + "sha256": "" + }, + "apkInfo": { + "appIcon": "https://pps-icon.png", + "appName": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "fileSize": 118902470, + "packageName": "com.demo.package", + "permPromptForCard": "0", + "popNotify": 1, + "popUpAfterInstallNew": 1, + "priorInstallWay": "2", + "sha256": "sha256", + "url": "https://test/apkurl", + "versionCode": "284", + "versionName": "9.6.1.9" + }, + "appId": "101219405", + "appPromotionChannel": "401721412", + "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/", + "cta": "%e5%ae%89%e8%a3%85", + "duration": 6038, + "icon": [ + { + "checkSha256Flag": 1, + "fileSize": 10797, + "height": 160, + "imageType": "img", + "sha256": "042479eccbda9a8d7d3aa3da73c42486854407835623a30ffff875cb578242d0", + "url": "https://pps-icon.png", + "width": 160 + } + ], + "imageInfo": [ + { + "checkSha256Flag": 0, + "height": 350, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image1.jpg", + "width": 400 + }, + { + "checkSha256Flag": 0, + "height": 300, + "imageType": "img", + "sha256": "8baa56fdb2702b9fb044d95b328936160cd245764375cdb25a4ab504f4ae2e19", + "url": "http://image2.jpg", + "width": 400 + } + ], + "label": "%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D", + "landingPageType": "3", + "marketAppId": "C101219405", + "title": "%2Ftest%2F", + "description": "this is a test ad", + "videoInfo": { + "autoPlayAreaRatio": 100, + "autoStopPlayAreaRatio": 10, + "checkSha256Flag": 1, + "sha256": "aa08c8ffce82bbcd37cabefd6c8972b407de48f0b4e332e06d4cc18d25377d77", + "timeBeforeVideoAutoPlay": 50, + "videoAutoPlayOnWifi": "y", + "videoAutoPlayWithSound": "n", + "videoDownloadUrl": "https://video.mp4", + "videoDuration": 6038, + "videoFileSize": 949951, + "videoPlayMode": 2, + "videoRatio": 0.5625, + "width": 600, + "height": 500 + } + }, + "monitor": [ + { + "eventType": "vastError", + "url": [ + "http://test/vastError" + ] + }, + { + "eventType": "click", + "url": [ + "http://test/click", + "http://test/dspclick" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp", + "http://test/dspimp" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + }, + { + "eventType": "playStart", + "url": [ + "http://test/playStart" + ] + }, + { + "eventType": "playEnd", + "url": [ + "http://test/playEnd1", + "http://test/playEnd2" + ] + }, + { + "eventType": "playResume", + "url": [ + "http://test/playResume" + ] + }, + { + "eventType": "playPause", + "url": [ + "http://test/playPause" + ] + }, + { + "eventType": "appOpen", + "url": [ + "http://test/appOpen" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1620230400000, + "taskid": "48016632" + } + ], + "retcode30": 200, + "slotid": "m8x9x3roll" + } + ], + "noReportAdTypeEventList": [ + { + "adType": 8, + "eventTypeList": [ + "exception_6" + ] + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "HuaweiAds/test/00:00:06.038 ", + "adomain": [ + "huaweiads" + ], + "h": 1280, + "w": 720, + "crid": "58001445", + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 2.8 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/adtype_roll_missing_duration.json b/adapters/huaweiads/huaweiadstest/supplemental/adtype_roll_missing_duration.json new file mode 100644 index 00000000000..9f41a7bc3fa --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/adtype_roll_missing_duration.json @@ -0,0 +1,68 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "roll", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "GetHuaweiAdsReqAdslot30: MaxDuration is empty when adtype is roll.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response.json new file mode 100644 index 00000000000..b064adb25a9 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response.json @@ -0,0 +1,228 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "format": [ + { + "h": 250, + "w": 300 + } + ], + "h": 250, + "w": 300 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 61, + "dspcost": 108, + "multiadw": [ + { + "adtype": 8, + "brsetting": "Y", + "content": [ + { + "clickActionList": [ + 1 + ], + "contentid": "58025103", + "creativetype": 2, + "ctrlSwitchs": "001011001001010112", + "endtime": 1621428898335, + "filterList": [ + 3 + ], + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "appPromotionChannel": "401721412", + "clickUrl": "https://task/clickurl", + "imageInfo": [ + { + "checkSha256Flag": 1, + "height": 300, + "imageType": "img", + "sha256": "f5d8487cddaecec45b73fc078649478c80ec646bfec8ed7da4ff931f90eab232", + "url": "https://test/1.png", + "width": 250 + } + ], + "label": "Banner_API", + "landingPageType": "3" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "m8x9x3rzff" + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + { + "value": "convertHuaweiAdsResp2BidderResp: multiad length is 0, get no ads from huawei side.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_400.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_400.json new file mode 100644 index 00000000000..6b42905b934 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_400.json @@ -0,0 +1,153 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "format": [ + { + "h": 250, + "w": 300 + } + ], + "h": 250, + "w": 300 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "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/huaweiads/huaweiadstest/supplemental/bad_response_503.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_503.json new file mode 100644 index 00000000000..032c8e9a933 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_503.json @@ -0,0 +1,153 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "format": [ + { + "h": 250, + "w": 300 + } + ], + "h": 250, + "w": 300 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 503, + "body": {} + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong, please contact your Account Manager. Status Code: [ 503 ] ", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_dont_find_impid.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_dont_find_impid.json new file mode 100644 index 00000000000..7d691d1138b --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_dont_find_impid.json @@ -0,0 +1,324 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzf1", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzf2", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzf1", + "test": 0, + "format": [ + { + "h": 250, + "w": 300 + } + ], + "h": 250, + "w": 300 + }, + { + "adtype": 8, + "slotid": "m8x9x3rzf2", + "test": 0, + "format": [ + { + "h": 250, + "w": 300 + } + ], + "h": 250, + "w": 300 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 61, + "dspcost": 108, + "multiad": [ + { + "adtype": 8, + "content": [ + { + "clickActionList": [ + 1 + ], + "contentid": "58025103", + "creativetype": 2, + "ctrlSwitchs": "001011001001010112", + "endtime": 1621428898335, + "filterList": [ + 3 + ], + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "appPromotionChannel": "401721412", + "clickUrl": "https://task/clickurl", + "imageInfo": [ + { + "checkSha256Flag": 1, + "height": 300, + "imageType": "img", + "sha256": "f5d8487cddaecec45b73fc078649478c80ec646bfec8ed7da4ff931f90eab232", + "url": "https://test/1.png", + "width": 250 + } + ], + "label": "Banner_API", + "landingPageType": "3" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "m8x9x3rzf1" + }, + { + "adtype": 8, + "content": [ + { + "clickActionList": [ + 1 + ], + "contentid": "58025103", + "creativetype": 2, + "ctrlSwitchs": "001011001001010112", + "endtime": 1621428898335, + "filterList": [ + 3 + ], + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "appPromotionChannel": "401721412", + "clickUrl": "https://task/clickurl", + "imageInfo": [ + { + "checkSha256Flag": 1, + "height": 300, + "imageType": "img", + "sha256": "f5d8487cddaecec45b73fc078649478c80ec646bfec8ed7da4ff931f90eab232", + "url": "https://test/1.png", + "width": 250 + } + ], + "label": "Banner_API", + "landingPageType": "3" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "m8x9x3rzf3" + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json new file mode 100644 index 00000000000..e4e6ec181b6 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json @@ -0,0 +1,219 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":101,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":107,\"video\":{\"mimes\":[\"mp4\"],\"minduration\":100,\"maxduration\":100,\"protocols\":[1,2]},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "detailedCreativeTypeList": [ + "903" + ], + "h": 200, + "w": 200 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "ctrlSwitchs": "0", + "dsp1cost": 61, + "dspcost": 108, + "multiad": [ + { + "adtype": 8, + "brsetting": "Y", + "content": [ + { + "clickActionList": [ + 1 + ], + "contentid": "58025103", + "creativetype": 2, + "ctrlSwitchs": "001011001001010112", + "endtime": 1621428898335, + "filterList": [ + 3 + ], + "interactiontype": 1, + "landingTitle": 1, + "metaData": { + "adSign": "2", + "appPromotionChannel": "401721412", + "clickUrl": "https://task/clickurl", + "imageInfo": [ + { + "checkSha256Flag": 1, + "height": 300, + "imageType": "img", + "sha256": "f5d8487cddaecec45b73fc078649478c80ec646bfec8ed7da4ff931f90eab232", + "url": "https://test/1.png", + "width": 250 + } + ], + "label": "Banner_API", + "landingPageType": "3" + }, + "monitor": [ + { + "eventType": "click", + "url": [ + "http://test/click" + ] + }, + { + "eventType": "imp", + "url": [ + "http://test/imp" + ] + }, + { + "eventType": "userclose", + "url": [ + "http://test/userclose" + ] + } + ], + "paramfromserver": { + "a": "1||test", + "sig": "", + "t": "99990101235959" + }, + "price": 2.8, + "starttime": 1621267200000, + "taskid": "48017658" + } + ], + "retcode30": 200, + "slotid": "m8x9x3rzff" + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + { + "value": "getAdmFromHuaweiAdsContent failed: extractAdmNative: response is not a native ad", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode30_204.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode30_204.json new file mode 100644 index 00000000000..5a04ff6b1b4 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode30_204.json @@ -0,0 +1,162 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "format": [ + { + "h": 250, + "w": 300 + } + ], + "h": 250, + "w": 300 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "multiad": [ + { + "adtype": 8, + "brsetting": "Y", + "content": [ + ], + "retcode30": 204, + "slotid": "m8x9x3rzff" + } + ], + "retcode": 200, + "totalCacheSize": 300 + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_210.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_210.json new file mode 100644 index 00000000000..ff0fd39fb74 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_210.json @@ -0,0 +1,156 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "format": [ + { + "h": 250, + "w": 300 + } + ], + "h": 250, + "w": 300 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "retcode": 210, + "reason": "Non-business error, ad not matched" + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + { + "value": "HuaweiAdsResponse retcode: 210 , reason: Non-business error, ad not matched", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_408.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_408.json new file mode 100644 index 00000000000..7878aa88fd0 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_408.json @@ -0,0 +1,156 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "format": [ + { + "h": 250, + "w": 300 + } + ], + "h": 250, + "w": 300 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "retcode": 408, + "reason": "SDK Version Missing" + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + { + "value": "HuaweiAdsResponse retcode: 408 , reason: SDK Version Missing", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_500.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_500.json new file mode 100644 index 00000000000..c3b9de07612 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_retcode_500.json @@ -0,0 +1,156 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://huaweiads.com/adxtest/", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "User-Agent": [ + "useragent" + ], + "Authorization": [ + "Digest username=123,realm=ppsadx/getResult,nonce=1629473330823,response=d1d61a13a83e1468aa4dff5c8a6cee0b8b381173ca3eb6fa9b313937684d87c0,algorithm=HmacSHA256,usertype=1,keyid=41" + ] + }, + "body": { + "app": { + "lang": "en", + "country": "ZA", + "name": "Huawei Browser", + "pkgname": "com.wavehk.android", + "version": "9.1.0.301" + }, + "multislot": [ + { + "adtype": 8, + "slotid": "m8x9x3rzff", + "test": 0, + "format": [ + { + "h": 250, + "w": 300 + } + ], + "h": 250, + "w": 300 + } + ], + "device": { + "height": 1920, + "language": "zh", + "oaid": "oaid", + "os": "android", + "type": 4, + "ip": "ip", + "localeCountry": "ZA", + "pxratio": 23.01, + "width": 1080, + "clientTime": "2018-11-02 16:34:07.981+1300", + "gaid": "gaid", + "useragent": "useragent", + "version": "10.0.0", + "maker": "huawei", + "model": "COL-TEST", + "belongCountry": "ZA" + }, + "geo": { + }, + "network": { + "type": 0 + }, + "regs": { + }, + "version": "3.4" + } + }, + "mockResponse": { + "status": 200, + "body": { + "retcode": 500, + "reason": "Internal System Error" + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [], + "expectedMakeBidsErrors": [ + { + "value": "HuaweiAdsResponse retcode: 500 , reason: Internal System Error", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_adtype.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_adtype.json new file mode 100644 index 00000000000..84365607b76 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_adtype.json @@ -0,0 +1,74 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "", + "publisherid": "123", + "signkey": "signkey", + "keyid": "12", + "clienttime": "2018-11-02 16:34:07.981+1300", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "152.193.6.74", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + ], + "oaid": [ + "oaid" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [{ + "value": "ExtImpHuaweiAds: adtype is empty.", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid.json new file mode 100644 index 00000000000..83b69b48d77 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_deviceid.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + ], + "oaid": [ + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "getDeviceID: Imei ,Oaid, Gaid are all empty.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_keyid.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_keyid.json new file mode 100644 index 00000000000..21d2bcd98db --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_keyid.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "152.193.6.74", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "ExtImpHuaweiAds: keyid is empty.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_native_request.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_native_request.json new file mode 100644 index 00000000000..d195f3fb59f --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_native_request.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "", + "ver": "1.2" + }, + "ext": { + "bidder": { + "slotid": "u42ohmaufh", + "adtype": "native", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.huawei.browser", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "152.193.6.74", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token", + "ext": { + "data": { + "gaid": [ + "gaid" + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [{ + "value": "extractAdmNative: imp.Native.Request is empty", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_pulisherid.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_pulisherid.json new file mode 100644 index 00000000000..5f7b434f62e --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_pulisherid.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "", + "signkey": "signkey", + "keyid": "12", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "ExtHuaweiAds: publisherid is empty.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_signkey.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_signkey.json new file mode 100644 index 00000000000..4c172e18f92 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_signkey.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "m8x9x3rzff", + "adtype": "banner", + "publisherid": "123", + "signkey": "", + "keyid": "12", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "ExtHuaweiAds: signkey is empty.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/huaweiadstest/supplemental/missing_slotid.json b/adapters/huaweiads/huaweiadstest/supplemental/missing_slotid.json new file mode 100644 index 00000000000..200b3ee0ce8 --- /dev/null +++ b/adapters/huaweiads/huaweiadstest/supplemental/missing_slotid.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "slotid": "", + "adtype": "banner", + "publisherid": "123", + "signkey": "signkey", + "keyid": "41", + "isTestAuthorization": "true" + } + } + } + ], + "app": { + "bundle": "com.wavehk.android", + "name": "Huawei Browser", + "ver": "9.1.0.301" + }, + "device": { + "ua": "useragent", + "h": 1920, + "language": "zh", + "geoCountry": "CH", + "model": "COL-TEST", + "os": "android", + "osv": "10.0.0", + "devicetype": 4, + "make": "huawei", + "w": 1080, + "ip": "ip", + "pxratio": 23.01, + "geo": { + "country": "" + } + }, + "user": { + "ext": { + "data": { + "gaid": [ + ], + "oaid": [ + "oaid" + ], + "clientTime": [ + "2018-11-02 16:34:07.981+1300" + ] + } + } + }, + "ext": { + } + }, + "httpcalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "ExtImpHuaweiAds: slotid is empty.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/huaweiads/params_test.go b/adapters/huaweiads/params_test.go new file mode 100644 index 00000000000..7347725b299 --- /dev/null +++ b/adapters/huaweiads/params_test.go @@ -0,0 +1,53 @@ +package huaweiads + +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.BidderHuaweiAds, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected huaweiads 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.BidderGumGum, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"slotid": "m8x9x3rzff","adtype": "banner","publisherid": "123","signkey": "2f910deac52ff34f0d80585d8664c55e3422ff3c6aeb5e1cf2ff94f1ac6a9642","keyid": "41","clienttime": "2018-11-02 16:34:07.981+1300"}`, + `{"slotid": "m8x9x3rzff","adtype": "banner","publisherid": "123","signkey": "2f910deac52ff34f0d80585d8664c55e3422ff3c6aeb5e1cf2ff94f1ac6a9642","keyid": "41"}`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"slotid": "","adtype": "banner","publisherid": "123","signkey": "2f910deac52ff34f0d80585d8664c55e3422ff3c6aeb5e1cf2ff94f1ac6a9642","keyid": "41","clienttime": "2018-11-02 16:34:07.981+1300"}`, + `{"slotid": "m8x9x3rzff","adtype": "","publisherid": "123","signkey": "2f910deac52ff34f0d80585d8664c55e3422ff3c6aeb5e1cf2ff94f1ac6a9642","keyid": "41","clienttime": "2018-11-02 16:34:07.981+1300"}`, + `{"slotid": "m8x9x3rzff","adtype": "banner","publisherid": "","signkey": "2f910deac52ff34f0d80585d8664c55e3422ff3c6aeb5e1cf2ff94f1ac6a9642","keyid": "41","clienttime": "2018-11-02 16:34:07.981+1300"}`, + `{"slotid": "m8x9x3rzff","adtype": "banner","publisherid": "123","signkey": "","keyid": "41","clienttime": "2018-11-02 16:34:07.981+1300"}`, + `{"slotid": "m8x9x3rzff","adtype": "banner","publisherid": "123","signkey": "2f910deac52ff34f0d80585d8664c55e3422ff3c6aeb5e1cf2ff94f1ac6a9642","keyid": "","clienttime": "2018-11-02 16:34:07.981+1300"}`, +} diff --git a/config/config.go b/config/config.go index f1ed1ff0a98..c701bcd5242 100644 --- a/config/config.go +++ b/config/config.go @@ -797,6 +797,8 @@ func SetupViper(v *viper.Viper, filename string) { 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.huaweiads.endpoint", "https://acd.op.hicloud.com/ppsadx/getResult") + v.SetDefault("adapters.huaweiads.disabled", true) 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://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index b25cff8c40e..ae7db1967d7 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -58,6 +58,7 @@ import ( "github.com/prebid/prebid-server/adapters/gamoshi" "github.com/prebid/prebid-server/adapters/grid" "github.com/prebid/prebid-server/adapters/gumgum" + "github.com/prebid/prebid-server/adapters/huaweiads" "github.com/prebid/prebid-server/adapters/improvedigital" "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/interactiveoffers" @@ -186,6 +187,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderGamoshi: gamoshi.Builder, openrtb_ext.BidderGrid: grid.Builder, openrtb_ext.BidderGumGum: gumgum.Builder, + openrtb_ext.BidderHuaweiAds: huaweiads.Builder, openrtb_ext.BidderImprovedigital: improvedigital.Builder, openrtb_ext.BidderInMobi: inmobi.Builder, openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 79d9abda16e..760e520eca4 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -129,6 +129,7 @@ const ( BidderGamoshi BidderName = "gamoshi" BidderGrid BidderName = "grid" BidderGumGum BidderName = "gumgum" + BidderHuaweiAds BidderName = "huaweiads" BidderImprovedigital BidderName = "improvedigital" BidderInMobi BidderName = "inmobi" BidderInteractiveoffers BidderName = "interactiveoffers" @@ -257,6 +258,7 @@ func CoreBidderNames() []BidderName { BidderGamoshi, BidderGrid, BidderGumGum, + BidderHuaweiAds, BidderImprovedigital, BidderInMobi, BidderInteractiveoffers, diff --git a/openrtb_ext/imp_huaweiads.go b/openrtb_ext/imp_huaweiads.go new file mode 100644 index 00000000000..c5d42dc5b57 --- /dev/null +++ b/openrtb_ext/imp_huaweiads.go @@ -0,0 +1,21 @@ +package openrtb_ext + +type ExtImpHuaweiAds struct { + SlotId string `json:"slotid"` + Adtype string `json:"adtype"` + PublisherId string `json:"publisherid"` + SignKey string `json:"signkey"` + KeyId string `json:"keyid"` + IsTestAuthorization string `json:"isTestAuthorization,omitempty"` +} + +type ExtUserDataHuaweiAds struct { + Data ExtUserDataDeviceIdHuaweiAds `json:"data,omitempty"` +} + +type ExtUserDataDeviceIdHuaweiAds struct { + Imei []string `json:"imei,omitempty"` + Oaid []string `json:"oaid,omitempty"` + Gaid []string `json:"gaid,omitempty"` + ClientTime []string `json:"clientTime,omitempty"` +} diff --git a/static/bidder-info/huaweiads.yaml b/static/bidder-info/huaweiads.yaml new file mode 100644 index 00000000000..a77bf4e0a74 --- /dev/null +++ b/static/bidder-info/huaweiads.yaml @@ -0,0 +1,9 @@ +maintainer: + email: hwads@huawei.com +gvlVendorID: 856 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - native \ No newline at end of file diff --git a/static/bidder-params/huaweiads.json b/static/bidder-params/huaweiads.json new file mode 100644 index 00000000000..c64eba78b80 --- /dev/null +++ b/static/bidder-params/huaweiads.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "HuaweiAds Adapter Params", + "description": "A schema which validates params accepted by the HuaweiAds adapter", + "type": "object", + "properties": { + "publisherid": { + "type": "string", + "description": "publisher id", + "minLength": 1 + }, + "signkey": { + "type": "string", + "description": "the sign key of publisher", + "minLength": 1 + }, + "keyid": { + "type": "string", + "description": "the key id of publisher", + "minLength": 1 + }, + "slotid": { + "type": "string", + "description": "ad slot id", + "minLength": 1 + }, + "adtype": { + "type": "string", + "description": "ad type", + "minLength": 1 + } + }, + "required": [ + "publisherid", + "signkey", + "keyid", + "slotid", + "adtype" + ] +} \ No newline at end of file From 93878665c162eebc08b9e8739b63807b8290e3fd Mon Sep 17 00:00:00 2001 From: vrtcal-dev <50931150+vrtcal-dev@users.noreply.github.com> Date: Thu, 26 Aug 2021 08:57:19 -0500 Subject: [PATCH 070/140] Added Video(VAST) Support (Rebased from master) (#1976) --- adapters/vrtcal/vrtcal.go | 39 +++++++- .../vrtcaltest/exemplary/simple-video.json | 96 +++++++++++++++++++ .../unmatched_response_impression_id.json | 87 +++++++++++++++++ static/bidder-info/vrtcal.yaml | 3 +- 4 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 adapters/vrtcal/vrtcaltest/exemplary/simple-video.json create mode 100644 adapters/vrtcal/vrtcaltest/supplemental/unmatched_response_impression_id.json diff --git a/adapters/vrtcal/vrtcal.go b/adapters/vrtcal/vrtcal.go index e4986b2f6fb..ce8392a3bbd 100644 --- a/adapters/vrtcal/vrtcal.go +++ b/adapters/vrtcal/vrtcal.go @@ -68,15 +68,21 @@ func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + var errs []error for _, sb := range bidResp.SeatBid { for i := range sb.Bid { - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &sb.Bid[i], - BidType: "banner", - }) + bidType, err := getReturnTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err == nil { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + }) + } else { + errs = append(errs, err) + } } } - return bidResponse, nil + return bidResponse, errs } @@ -87,3 +93,26 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } return bidder, nil } + +func getReturnTypeForImp(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 + } + + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unsupported return type for ID: \"%s\"", impID), + } + } + } + + //Failsafe default in case impression ID is not found + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), + } +} diff --git a/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json b/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json new file mode 100644 index 00000000000..365c892b87f --- /dev/null +++ b/adapters/vrtcal/vrtcaltest/exemplary/simple-video.json @@ -0,0 +1,96 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "Just_an_unused_vrtcal_param": "unused_data_for_this" + } + } + } + ], + "app": { + "id": "fake-app-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + + "ext": { + "bidder": { + "Just_an_unused_vrtcal_param": "unused_data_for_this" + } + } + } + ], + "app": { + "id": "fake-app-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "vrtcal", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "adm": "some-test-video-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-video-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/vrtcal/vrtcaltest/supplemental/unmatched_response_impression_id.json b/adapters/vrtcal/vrtcaltest/supplemental/unmatched_response_impression_id.json new file mode 100644 index 00000000000..42cf5f1a0f2 --- /dev/null +++ b/adapters/vrtcal/vrtcaltest/supplemental/unmatched_response_impression_id.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "Just_an_unused_vrtcal_param": "unused_data_for_this" + } + } + } + ], + "app": { + "id": "fake-app-id" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + + "ext": { + "bidder": { + "Just_an_unused_vrtcal_param": "unused_data_for_this" + } + } + } + ], + "app": { + "id": "fake-app-id" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "vrtcal", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "WRONG-IMPRESSION_ID", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression for ID: \"WRONG-IMPRESSION_ID\"", + "comparison": "literal" + } + ] +} diff --git a/static/bidder-info/vrtcal.yaml b/static/bidder-info/vrtcal.yaml index 9a33e9f941a..397d0c5e6f8 100644 --- a/static/bidder-info/vrtcal.yaml +++ b/static/bidder-info/vrtcal.yaml @@ -4,8 +4,9 @@ capabilities: app: mediaTypes: - banner + - video userSync: # vrtcal supports user syncing, but requires configuration by the host. contact this # bidder directly at the email address in this file to ask about enabling user sync. supports: - - redirect \ No newline at end of file + - redirect From aa89cedc9400cf3b02a54257d565f2541b2ea19b Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 26 Aug 2021 10:10:58 -0400 Subject: [PATCH 071/140] SmarHub: extra test to add coverage for unknown bid type scenario (#1954) --- .../supplemental/wrong-bidtype.json | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 adapters/smarthub/smarthubtest/supplemental/wrong-bidtype.json diff --git a/adapters/smarthub/smarthubtest/supplemental/wrong-bidtype.json b/adapters/smarthub/smarthubtest/supplemental/wrong-bidtype.json new file mode 100644 index 00000000000..7c65602c195 --- /dev/null +++ b/adapters/smarthub/smarthubtest/supplemental/wrong-bidtype.json @@ -0,0 +1,140 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer", + "body": { + "id": "id", + "imp": [ + { + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "partnerName": "partnertest", + "seat": "9Q20EdGxzgWdfPYShScl", + "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer" + } + } + } + ], + "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": "unknown" + } + } + ], + "seat": "seat" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "invalid BidType: unknown", + "comparison": "literal" + } + ], + "expectedBidResponses": [] +} From e3590c3c1d9d89fb02ec534cd773774917d36599 Mon Sep 17 00:00:00 2001 From: Mansi Nahar Date: Thu, 26 Aug 2021 11:00:34 -0400 Subject: [PATCH 072/140] Update iOS version parsing for LMT tracking to accept major.minor.patch format as well (#1978) --- go.mod | 1 + go.sum | 2 + privacy/lmt/ios_test.go | 48 +++++++++++++++++++ util/iosutil/iosutil.go | 28 ++++++----- util/iosutil/iosutil_test.go | 92 ++++++++++++++++++++++++++++++++++-- 5 files changed, 155 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index d003abae24d..6c6f32090ba 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b 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.5 github.com/mattn/go-colorable v0.1.2 // indirect diff --git a/go.sum b/go.sum index 343bcf0d8f8..6db57ada702 100644 --- a/go.sum +++ b/go.sum @@ -205,6 +205,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4EI5MABq68= 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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= diff --git a/privacy/lmt/ios_test.go b/privacy/lmt/ios_test.go index 2caec1be64c..af72cf08f23 100644 --- a/privacy/lmt/ios_test.go +++ b/privacy/lmt/ios_test.go @@ -32,6 +32,54 @@ func TestModifyForIOS(t *testing.T) { }, expectedLMT: openrtb2.Int8Ptr(1), }, + { + description: "14.1", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "14.1", IFA: "", Lmt: nil}, + }, + expectedLMT: openrtb2.Int8Ptr(1), + }, + { + description: "14.1.3", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "14.1.3", IFA: "", Lmt: nil}, + }, + expectedLMT: openrtb2.Int8Ptr(1), + }, + { + description: "14.2", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{Ext: json.RawMessage(`{"atts":0}`), OS: "iOS", OSV: "14.2", IFA: "", Lmt: nil}, + }, + expectedLMT: openrtb2.Int8Ptr(0), + }, + { + description: "14.2", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{Ext: json.RawMessage(`{"atts":2}`), OS: "iOS", OSV: "14.2", IFA: "", Lmt: openrtb2.Int8Ptr(0)}, + }, + expectedLMT: openrtb2.Int8Ptr(1), + }, + { + description: "14.2.7", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{Ext: json.RawMessage(`{"atts":1}`), OS: "iOS", OSV: "14.2.7", IFA: "", Lmt: nil}, + }, + expectedLMT: openrtb2.Int8Ptr(1), + }, + { + description: "14.2.7", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{Ext: json.RawMessage(`{"atts":3}`), OS: "iOS", OSV: "14.2.7", IFA: "", Lmt: openrtb2.Int8Ptr(1)}, + }, + expectedLMT: openrtb2.Int8Ptr(0), + }, } for _, test := range testCases { diff --git a/util/iosutil/iosutil.go b/util/iosutil/iosutil.go index 19d7c53d99a..86add49cede 100644 --- a/util/iosutil/iosutil.go +++ b/util/iosutil/iosutil.go @@ -16,8 +16,8 @@ type Version struct { func ParseVersion(v string) (Version, error) { parts := strings.Split(v, ".") - if len(parts) != 2 { - return Version{}, errors.New("expected major.minor format") + if len(parts) < 2 || len(parts) > 3 { + return Version{}, errors.New("expected either major.minor or major.minor.patch format") } major, err := strconv.Atoi(parts[0]) @@ -37,6 +37,15 @@ func ParseVersion(v string) (Version, error) { return version, nil } +// Equal returns true if the iOS device version is equal to the desired major and minor version, using semantic versioning. +func (v Version) Equal(major, minor int) bool { + if v.Major == major { + return v.Minor == minor + } + + return false +} + // EqualOrGreater returns true if the iOS device version is equal or greater to the desired version, using semantic versioning. func (v Version) EqualOrGreater(major, minor int) bool { if v.Major == major { @@ -59,20 +68,17 @@ const ( // DetectVersionClassification detects the iOS version classification. func DetectVersionClassification(v string) VersionClassification { - // exact comparisons first. no parsing required. - if v == "14.0" { - return Version140 - } - if v == "14.1" { - return Version141 - } - // semantic versioning comparison second. parsing required. if iosVersion, err := ParseVersion(v); err == nil { + if iosVersion.Equal(14, 0) { + return Version140 + } + if iosVersion.Equal(14, 1) { + return Version141 + } if iosVersion.EqualOrGreater(14, 2) { return Version142OrGreater } } - return VersionUnknown } diff --git a/util/iosutil/iosutil_test.go b/util/iosutil/iosutil_test.go index 2103760c1dc..290b16ad559 100644 --- a/util/iosutil/iosutil_test.go +++ b/util/iosutil/iosutil_test.go @@ -14,24 +14,34 @@ func TestParseVersion(t *testing.T) { expectedError string }{ { - description: "Valid", + description: "Valid - major.minor format", given: "14.2", expectedVersion: Version{Major: 14, Minor: 2}, }, + { + description: "Valid - major.minor.patch format", + given: "14.2.1", + expectedVersion: Version{Major: 14, Minor: 2}, + }, { description: "Invalid Parts - Empty", given: "", - expectedError: "expected major.minor format", + expectedError: "expected either major.minor or major.minor.patch format", }, { description: "Invalid Parts - Too Few", given: "14", - expectedError: "expected major.minor format", + expectedError: "expected either major.minor or major.minor.patch format", }, { description: "Invalid Parts - Too Many", - given: "14.2.1", - expectedError: "expected major.minor format", + given: "14.2.1.3", + expectedError: "expected either major.minor or major.minor.patch format", + }, + { + description: "Invalid Parts - Too Few", + given: "14", + expectedError: "expected either major.minor or major.minor.patch format", }, { description: "Invalid Major", @@ -110,6 +120,58 @@ func TestEqualOrGreater(t *testing.T) { } } +func TestEqual(t *testing.T) { + givenMajor := 14 + givenMinor := 2 + + tests := []struct { + description string + givenVersion Version + expected bool + }{ + { + description: "Less Than By Major + Minor", + givenVersion: Version{Major: 13, Minor: 1}, + expected: false, + }, + { + description: "Less Than By Major", + givenVersion: Version{Major: 13, Minor: 2}, + expected: false, + }, + { + description: "Less Than By Minor", + givenVersion: Version{Major: 14, Minor: 1}, + expected: false, + }, + { + description: "Equal", + givenVersion: Version{Major: 14, Minor: 2}, + expected: true, + }, + { + description: "Greater By Major + Minor", + givenVersion: Version{Major: 15, Minor: 3}, + expected: false, + }, + { + description: "Greater By Major", + givenVersion: Version{Major: 15, Minor: 2}, + expected: false, + }, + { + description: "Greater By Minor", + givenVersion: Version{Major: 14, Minor: 3}, + expected: false, + }, + } + + for _, test := range tests { + result := test.givenVersion.Equal(givenMajor, givenMinor) + assert.Equal(t, test.expected, result, test.description) + } +} + func TestDetectVersionClassification(t *testing.T) { tests := []struct { @@ -124,22 +186,42 @@ func TestDetectVersionClassification(t *testing.T) { given: "14.0", expected: Version140, }, + { + given: "14.0.1", + expected: Version140, + }, { given: "14.1", expected: Version141, }, + { + given: "14.1.2", + expected: Version141, + }, { given: "14.2", expected: Version142OrGreater, }, + { + given: "14.2.3", + expected: Version142OrGreater, + }, { given: "14.3", expected: Version142OrGreater, }, + { + given: "14.3.2", + expected: Version142OrGreater, + }, { given: "15.0", expected: Version142OrGreater, }, + { + given: "15.0.1", + expected: Version142OrGreater, + }, } for _, test := range tests { From 1bddd7baf50af082b4ff643e91490a0e5dcc9375 Mon Sep 17 00:00:00 2001 From: Rachel Joyce Date: Tue, 31 Aug 2021 11:59:36 -0600 Subject: [PATCH 073/140] Add test to confirm fpd sent through to sovrn (#1926) Co-authored-by: Rachel Joyce --- .../sovrn/sovrntest/supplemental/fpd.json | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 adapters/sovrn/sovrntest/supplemental/fpd.json diff --git a/adapters/sovrn/sovrntest/supplemental/fpd.json b/adapters/sovrn/sovrntest/supplemental/fpd.json new file mode 100644 index 00000000000..005d159d740 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/fpd.json @@ -0,0 +1,168 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "tagid": "123456", + "anotherid": "654321" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site", + "keywords": "word1", + "search": "search", + "ext": { + "data": { + "example-site-ext-data-key": "example-site-ext-data-value" + } + } + }, + "user": { + "buyeruid": "test_reader_id", + "keywords": "word2", + "gender": "female", + "yob": 1992, + "ext": { + "data": { + "example-user-ext-data-key": "example-site-ext-data-value" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "User-Agent": ["test-user-agent"], + "X-Forwarded-For": ["123.123.123.123"], + "Accept-Language": ["en"], + "Dnt": ["0"], + "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", + "anotherid": "654321" + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site", + "keywords": "word1", + "search": "search", + "ext": { + "data": { + "example-site-ext-data-key": "example-site-ext-data-value" + } + } + }, + "user": { + "buyeruid": "test_reader_id", + "keywords": "word2", + "gender": "female", + "yob": 1992, + "ext": { + "data": { + "example-user-ext-data-key": "example-site-ext-data-value" + } + } + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "dnt": 0, + "language": "en" + } + } + }, + "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" + } + ] + } + ] +} From 9a9f355a929da1274cd9bf36e48968373adc00ee Mon Sep 17 00:00:00 2001 From: oath-jac <45564796+oath-jac@users.noreply.github.com> Date: Thu, 2 Sep 2021 18:22:01 +0300 Subject: [PATCH 074/140] New Adapter: YSSP (First Step In Rebranding From VerizonMedia) (#1946) Co-authored-by: oath-jac --- .../supplemental/required-noimp.json | 14 ----- .../{verizonmedia => yssp}/params_test.go | 16 +++--- .../verizonmedia.go => yssp/yssp.go} | 52 +++++-------------- .../yssp_test.go} | 14 ++--- .../exemplary/simple-app-banner.json | 2 +- .../yssptest}/exemplary/simple-banner.json | 2 +- .../yssp/yssptest/params/race/banner.json | 4 ++ .../supplemental/empty-banner-format.json} | 4 +- .../supplemental/invalid-banner-width.json} | 5 +- .../supplemental/non-banner-bids-ignored.json | 2 +- .../supplemental/required-nobidder-info.json | 0 .../yssptest}/supplemental/server-error.json | 0 .../server-response-wrong-impid.json | 2 +- config/config.go | 1 + exchange/adapter_builders.go | 5 +- openrtb_ext/bidders.go | 2 + openrtb_ext/imp_yssp.go | 7 +++ static/bidder-info/yssp.yaml | 15 ++++++ static/bidder-params/yssp.json | 19 +++++++ 19 files changed, 89 insertions(+), 77 deletions(-) delete mode 100644 adapters/verizonmedia/verizonmediatest/supplemental/required-noimp.json rename adapters/{verizonmedia => yssp}/params_test.go (60%) rename adapters/{verizonmedia/verizonmedia.go => yssp/yssp.go} (72%) rename adapters/{verizonmedia/verizonmedia_test.go => yssp/yssp_test.go} (53%) rename adapters/{verizonmedia/verizonmediatest => yssp/yssptest}/exemplary/simple-app-banner.json (98%) rename adapters/{verizonmedia/verizonmediatest => yssp/yssptest}/exemplary/simple-banner.json (98%) create mode 100644 adapters/yssp/yssptest/params/race/banner.json rename adapters/{verizonmedia/verizonmediatest/supplemental/required-params-pos.json => yssp/yssptest/supplemental/empty-banner-format.json} (79%) rename adapters/{verizonmedia/verizonmediatest/supplemental/required-params-dcn.json => yssp/yssptest/supplemental/invalid-banner-width.json} (77%) rename adapters/{verizonmedia/verizonmediatest => yssp/yssptest}/supplemental/non-banner-bids-ignored.json (98%) rename adapters/{verizonmedia/verizonmediatest => yssp/yssptest}/supplemental/required-nobidder-info.json (100%) rename adapters/{verizonmedia/verizonmediatest => yssp/yssptest}/supplemental/server-error.json (100%) rename adapters/{verizonmedia/verizonmediatest => yssp/yssptest}/supplemental/server-response-wrong-impid.json (98%) create mode 100644 openrtb_ext/imp_yssp.go create mode 100644 static/bidder-info/yssp.yaml create mode 100644 static/bidder-params/yssp.json diff --git a/adapters/verizonmedia/verizonmediatest/supplemental/required-noimp.json b/adapters/verizonmedia/verizonmediatest/supplemental/required-noimp.json deleted file mode 100644 index 4f1b3e5ce31..00000000000 --- a/adapters/verizonmedia/verizonmediatest/supplemental/required-noimp.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - ] - }, - - "expectedMakeRequestsErrors": [ - { - "value": "No impression in the bid request", - "comparison": "literal" - } - ] -} diff --git a/adapters/verizonmedia/params_test.go b/adapters/yssp/params_test.go similarity index 60% rename from adapters/verizonmedia/params_test.go rename to adapters/yssp/params_test.go index febda6058e6..66146377f0c 100644 --- a/adapters/verizonmedia/params_test.go +++ b/adapters/yssp/params_test.go @@ -1,4 +1,4 @@ -package verizonmedia +package yssp import ( "encoding/json" @@ -7,11 +7,11 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -// This file actually intends to test static/bidder-params/verizonmedia.json +// This file actually intends to test static/bidder-params/yssp.json // -// These also validate the format of the external API: request.imp[i].ext.verizonmedia +// These also validate the format of the external API: request.imp[i].ext.yssp -// TestValidParams makes sure that the verizonmedia schema accepts all imp.ext fields which we intend to support. +// TestValidParams makes sure that the yssp 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 { @@ -19,13 +19,13 @@ func TestValidParams(t *testing.T) { } for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderVerizonMedia, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected verizonmedia params: %s", validParam) + if err := validator.Validate(openrtb_ext.BidderYSSP, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected yssp params: %s", validParam) } } } -// TestInvalidParams makes sure that the verizonmedia schema rejects all the imp.ext fields we don't support. +// TestInvalidParams makes sure that the yssp 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 { @@ -33,7 +33,7 @@ func TestInvalidParams(t *testing.T) { } for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderVerizonMedia, json.RawMessage(invalidParam)); err == nil { + if err := validator.Validate(openrtb_ext.BidderYSSP, json.RawMessage(invalidParam)); err == nil { t.Errorf("Schema allowed unexpected params: %s", invalidParam) } } diff --git a/adapters/verizonmedia/verizonmedia.go b/adapters/yssp/yssp.go similarity index 72% rename from adapters/verizonmedia/verizonmedia.go rename to adapters/yssp/yssp.go index aa215c01691..a73398ca3c0 100644 --- a/adapters/verizonmedia/verizonmedia.go +++ b/adapters/yssp/yssp.go @@ -1,8 +1,7 @@ -package verizonmedia +package yssp import ( "encoding/json" - "errors" "fmt" "net/http" @@ -13,20 +12,12 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -type VerizonMediaAdapter struct { +type adapter struct { URI string } -func (a *VerizonMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - errors := make([]error, 0, 1) - - if len(request.Imp) == 0 { - err := &errortypes.BadInput{ - Message: "No impression in the bid request", - } - errors = append(errors, err) - return nil, errors - } +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error reqs := make([]*adapters.RequestData, 0, len(request.Imp)) headers := http.Header{} @@ -50,8 +41,8 @@ func (a *VerizonMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo continue } - var verizonMediaExt openrtb_ext.ExtImpVerizonMedia - err = json.Unmarshal(bidderExt.Bidder, &verizonMediaExt) + var ysspExt openrtb_ext.ExtImpYSSP + err = json.Unmarshal(bidderExt.Bidder, &ysspExt) if err != nil { err = &errortypes.BadInput{ Message: fmt.Sprintf("imp #%d: %s", idx, err.Error()), @@ -60,22 +51,6 @@ func (a *VerizonMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo continue } - if verizonMediaExt.Dcn == "" { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("imp #%d: missing param dcn", idx), - } - errors = append(errors, err) - continue - } - - if verizonMediaExt.Pos == "" { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("imp #%d: missing param pos", idx), - } - errors = append(errors, err) - continue - } - // Split up multi-impression requests into multiple requests so that // each split request is only associated to a single impression reqCopy := *request @@ -89,7 +64,7 @@ func (a *VerizonMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo reqCopy.App = &appCopy } - if err := changeRequestForBidService(&reqCopy, &verizonMediaExt); err != nil { + if err := changeRequestForBidService(&reqCopy, &ysspExt); err != nil { errors = append(errors, err) continue } @@ -111,7 +86,7 @@ func (a *VerizonMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo return reqs, errors } -func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []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 @@ -134,6 +109,7 @@ func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, ext for _, sb := range bidResp.SeatBid { for _, bid := range sb.Bid { + bid := bid exists, mediaTypeId := getImpInfo(bid.ImpID, internalRequest.Imp) if !exists { return nil, []error{&errortypes.BadServerResponse{ @@ -171,7 +147,7 @@ func getImpInfo(impId string, imps []openrtb2.Imp) (bool, openrtb_ext.BidType) { return exists, mediaType } -func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpVerizonMedia) error { +func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpYSSP) error { /* Always override the tag ID and (site ID or app ID) of the request */ request.Imp[0].TagID = extension.Pos if request.Site != nil { @@ -189,13 +165,13 @@ func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb if banner.W != nil && banner.H != nil { if *banner.W == 0 || *banner.H == 0 { - return errors.New(fmt.Sprintf("Invalid sizes provided for Banner %dx%d", *banner.W, *banner.H)) + return fmt.Errorf("Invalid sizes provided for Banner %dx%d", *banner.W, *banner.H) } return nil } if len(banner.Format) == 0 { - return errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) + return fmt.Errorf("No sizes provided for Banner %v", banner.Format) } banner.W = openrtb2.Int64Ptr(banner.Format[0].W) @@ -204,9 +180,9 @@ func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb return nil } -// Builder builds a new instance of the VerizonMedia adapter for the given bidder with the given config. +// Builder builds a new instance of the YSSP adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - bidder := &VerizonMediaAdapter{ + bidder := &adapter{ URI: config.Endpoint, } return bidder, nil diff --git a/adapters/verizonmedia/verizonmedia_test.go b/adapters/yssp/yssp_test.go similarity index 53% rename from adapters/verizonmedia/verizonmedia_test.go rename to adapters/yssp/yssp_test.go index 869ae9e9faa..ee99d67211a 100644 --- a/adapters/verizonmedia/verizonmedia_test.go +++ b/adapters/yssp/yssp_test.go @@ -1,4 +1,4 @@ -package verizonmedia +package yssp import ( "testing" @@ -9,8 +9,8 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -func TestVerizonMediaBidderEndpointConfig(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderVerizonMedia, config.Adapter{ +func TestYSSPBidderEndpointConfig(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderYSSP, config.Adapter{ Endpoint: "http://localhost/bid", }) @@ -18,17 +18,17 @@ func TestVerizonMediaBidderEndpointConfig(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - bidderVerizonMedia := bidder.(*VerizonMediaAdapter) + bidderYSSP := bidder.(*adapter) - assert.Equal(t, "http://localhost/bid", bidderVerizonMedia.URI) + assert.Equal(t, "http://localhost/bid", bidderYSSP.URI) } func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderVerizonMedia, config.Adapter{}) + bidder, buildErr := Builder(openrtb_ext.BidderYSSP, config.Adapter{}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } - adapterstest.RunJSONBidderTest(t, "verizonmediatest", bidder) + adapterstest.RunJSONBidderTest(t, "yssptest", bidder) } diff --git a/adapters/verizonmedia/verizonmediatest/exemplary/simple-app-banner.json b/adapters/yssp/yssptest/exemplary/simple-app-banner.json similarity index 98% rename from adapters/verizonmedia/verizonmediatest/exemplary/simple-app-banner.json rename to adapters/yssp/yssptest/exemplary/simple-app-banner.json index dafa911d98e..a28913f8b9d 100644 --- a/adapters/verizonmedia/verizonmediatest/exemplary/simple-app-banner.json +++ b/adapters/yssp/yssptest/exemplary/simple-app-banner.json @@ -77,7 +77,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "verizonmedia", + "seat": "yssp", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/verizonmedia/verizonmediatest/exemplary/simple-banner.json b/adapters/yssp/yssptest/exemplary/simple-banner.json similarity index 98% rename from adapters/verizonmedia/verizonmediatest/exemplary/simple-banner.json rename to adapters/yssp/yssptest/exemplary/simple-banner.json index 95dd59528f2..4a71473b4b4 100644 --- a/adapters/verizonmedia/verizonmediatest/exemplary/simple-banner.json +++ b/adapters/yssp/yssptest/exemplary/simple-banner.json @@ -77,7 +77,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "verizonmedia", + "seat": "yssp", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yssp/yssptest/params/race/banner.json b/adapters/yssp/yssptest/params/race/banner.json new file mode 100644 index 00000000000..739ec3c024b --- /dev/null +++ b/adapters/yssp/yssptest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "dcn": "2345", + "pos": "header" +} diff --git a/adapters/verizonmedia/verizonmediatest/supplemental/required-params-pos.json b/adapters/yssp/yssptest/supplemental/empty-banner-format.json similarity index 79% rename from adapters/verizonmedia/verizonmediatest/supplemental/required-params-pos.json rename to adapters/yssp/yssptest/supplemental/empty-banner-format.json index 7757293ff31..6c4641d15dc 100644 --- a/adapters/verizonmedia/verizonmediatest/supplemental/required-params-pos.json +++ b/adapters/yssp/yssptest/supplemental/empty-banner-format.json @@ -5,7 +5,7 @@ { "id": "test-imp-id", "banner": { - "format": [{"w": 728, "h": 90}] + "format": [] }, "ext": { "bidder": { @@ -19,7 +19,7 @@ "expectedMakeRequestsErrors": [ { - "value": "imp #0: missing param pos", + "value": "No sizes provided for Banner []", "comparison": "literal" } ] diff --git a/adapters/verizonmedia/verizonmediatest/supplemental/required-params-dcn.json b/adapters/yssp/yssptest/supplemental/invalid-banner-width.json similarity index 77% rename from adapters/verizonmedia/verizonmediatest/supplemental/required-params-dcn.json rename to adapters/yssp/yssptest/supplemental/invalid-banner-width.json index 7f01bd04785..8d2f2504d07 100644 --- a/adapters/verizonmedia/verizonmediatest/supplemental/required-params-dcn.json +++ b/adapters/yssp/yssptest/supplemental/invalid-banner-width.json @@ -5,7 +5,8 @@ { "id": "test-imp-id", "banner": { - "format": [{"w": 728, "h": 90}] + "w": 0, + "h": 90 }, "ext": { "bidder": { @@ -18,7 +19,7 @@ "expectedMakeRequestsErrors": [ { - "value": "imp #0: missing param dcn", + "value": "Invalid sizes provided for Banner 0x90", "comparison": "literal" } ] diff --git a/adapters/verizonmedia/verizonmediatest/supplemental/non-banner-bids-ignored.json b/adapters/yssp/yssptest/supplemental/non-banner-bids-ignored.json similarity index 98% rename from adapters/verizonmedia/verizonmediatest/supplemental/non-banner-bids-ignored.json rename to adapters/yssp/yssptest/supplemental/non-banner-bids-ignored.json index 4a4b1e9a4a1..94772a4b039 100644 --- a/adapters/verizonmedia/verizonmediatest/supplemental/non-banner-bids-ignored.json +++ b/adapters/yssp/yssptest/supplemental/non-banner-bids-ignored.json @@ -74,7 +74,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "verizonmedia", + "seat": "yssp", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/verizonmedia/verizonmediatest/supplemental/required-nobidder-info.json b/adapters/yssp/yssptest/supplemental/required-nobidder-info.json similarity index 100% rename from adapters/verizonmedia/verizonmediatest/supplemental/required-nobidder-info.json rename to adapters/yssp/yssptest/supplemental/required-nobidder-info.json diff --git a/adapters/verizonmedia/verizonmediatest/supplemental/server-error.json b/adapters/yssp/yssptest/supplemental/server-error.json similarity index 100% rename from adapters/verizonmedia/verizonmediatest/supplemental/server-error.json rename to adapters/yssp/yssptest/supplemental/server-error.json diff --git a/adapters/verizonmedia/verizonmediatest/supplemental/server-response-wrong-impid.json b/adapters/yssp/yssptest/supplemental/server-response-wrong-impid.json similarity index 98% rename from adapters/verizonmedia/verizonmediatest/supplemental/server-response-wrong-impid.json rename to adapters/yssp/yssptest/supplemental/server-response-wrong-impid.json index 16cfb874e98..1e893127df4 100644 --- a/adapters/verizonmedia/verizonmediatest/supplemental/server-response-wrong-impid.json +++ b/adapters/yssp/yssptest/supplemental/server-response-wrong-impid.json @@ -76,7 +76,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "verizonmedia", + "seat": "yssp", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "wrong", diff --git a/config/config.go b/config/config.go index c701bcd5242..609fa9e6a13 100644 --- a/config/config.go +++ b/config/config.go @@ -868,6 +868,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/") v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server") v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp") + v.SetDefault("adapters.yssp.disabled", true) v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("max_request_size", 1024*256) diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index ae7db1967d7..74a1d257082 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -115,13 +115,13 @@ import ( "github.com/prebid/prebid-server/adapters/unicorn" "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/visx" "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/yeahmobi" "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" + "github.com/prebid/prebid-server/adapters/yssp" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -246,7 +246,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderUnicorn: unicorn.Builder, openrtb_ext.BidderUnruly: unruly.Builder, openrtb_ext.BidderValueImpression: valueimpression.Builder, - openrtb_ext.BidderVerizonMedia: verizonmedia.Builder, + openrtb_ext.BidderVerizonMedia: yssp.Builder, openrtb_ext.BidderViewdeos: adtelligent.Builder, openrtb_ext.BidderVisx: visx.Builder, openrtb_ext.BidderVrtcal: vrtcal.Builder, @@ -254,6 +254,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderYieldlab: yieldlab.Builder, openrtb_ext.BidderYieldmo: yieldmo.Builder, openrtb_ext.BidderYieldone: yieldone.Builder, + openrtb_ext.BidderYSSP: yssp.Builder, openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, } } diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 760e520eca4..dd4c1c86ae8 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -196,6 +196,7 @@ const ( BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" BidderYieldone BidderName = "yieldone" + BidderYSSP BidderName = "yssp" BidderZeroClickFraud BidderName = "zeroclickfraud" ) @@ -325,6 +326,7 @@ func CoreBidderNames() []BidderName { BidderYieldlab, BidderYieldmo, BidderYieldone, + BidderYSSP, BidderZeroClickFraud, } } diff --git a/openrtb_ext/imp_yssp.go b/openrtb_ext/imp_yssp.go new file mode 100644 index 00000000000..054836ec21d --- /dev/null +++ b/openrtb_ext/imp_yssp.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpYSSP defines the contract for bidrequest.imp[i].ext.yssp +type ExtImpYSSP struct { + Dcn string `json:"dcn"` + Pos string `json:"pos"` +} diff --git a/static/bidder-info/yssp.yaml b/static/bidder-info/yssp.yaml new file mode 100644 index 00000000000..a1a20e0c9d0 --- /dev/null +++ b/static/bidder-info/yssp.yaml @@ -0,0 +1,15 @@ +maintainer: + email: "hb-fe-tech@oath.com" +gvlVendorID: 25 +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner +userSync: + # yssp supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-params/yssp.json b/static/bidder-params/yssp.json new file mode 100644 index 00000000000..fdadc747a4c --- /dev/null +++ b/static/bidder-params/yssp.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "YSSP Adapter Params", + "description": "A schema which validates params accepted by the YSSP adapter", + "type": "object", + "properties": { + "dcn": { + "type": "string", + "minLength": 1, + "description": "Site ID provided by One Mobile" + }, + "pos": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + } + }, + "required": ["dcn", "pos"] +} From 42f5a57986899f49074901dcc70c733775488d45 Mon Sep 17 00:00:00 2001 From: IOTiagoFaria <76956619+IOTiagoFaria@users.noreply.github.com> Date: Thu, 2 Sep 2021 18:19:41 +0100 Subject: [PATCH 075/140] InteractiveOffers - Endpoint using macro (#1977) --- .../interactiveoffers/interactiveoffers.go | 45 ++++++++++++++++--- adapters/interactiveoffers/params_test.go | 15 ++++--- config/config.go | 2 +- openrtb_ext/imp_interactiveoffers.go | 2 +- static/bidder-info/interactiveoffers.yaml | 2 +- static/bidder-params/interactiveoffers.json | 8 ++-- 6 files changed, 56 insertions(+), 18 deletions(-) diff --git a/adapters/interactiveoffers/interactiveoffers.go b/adapters/interactiveoffers/interactiveoffers.go index fd4cd5807f5..e77dfd5a563 100644 --- a/adapters/interactiveoffers/interactiveoffers.go +++ b/adapters/interactiveoffers/interactiveoffers.go @@ -4,30 +4,54 @@ 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 string + endpoint *template.Template } func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + return nil, errors + } + var ioExt *openrtb_ext.ExtImpInteractiveoffers + if err := json.Unmarshal(bidderExt.Bidder, &ioExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + return nil, errors + } requestJSON, err := json.Marshal(request) if err != nil { return nil, []error{err} } + url, err := a.buildEndpointURL(ioExt) + if err != nil { + return nil, []error{err} + } + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") requestData := &adapters.RequestData{ - Method: "POST", - Uri: a.endpoint, - Body: requestJSON, + Method: "POST", + Uri: url, + Body: requestJSON, + Headers: headers, } - return []*adapters.RequestData{requestData}, nil } @@ -72,8 +96,17 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R // 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) { + 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: config.Endpoint, + endpoint: template, } return bidder, nil } + +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtImpInteractiveoffers) (string, error) { + endpointParams := macros.EndpointTemplateParams{AccountID: params.PartnerId} + return macros.ResolveMacros(a.endpoint, endpointParams) +} diff --git a/adapters/interactiveoffers/params_test.go b/adapters/interactiveoffers/params_test.go index 754be842a80..193c1b84839 100644 --- a/adapters/interactiveoffers/params_test.go +++ b/adapters/interactiveoffers/params_test.go @@ -33,12 +33,17 @@ func TestInvalidParams(t *testing.T) { } var validParams = []string{ - `{"pubid":35}`, + `{"partnerId":"abc123"}`, } var invalidParams = []string{ - `{"pubid":"35"}`, - `{"pubId":35}`, - `{"PubId":35}`, - `{}`, + ``, + `{"partnerId":35}`, + `{"partnerId":false}`, + `{"partnerId":null}`, + `null`, + `true`, + `0`, + `abc`, + `[]`, } diff --git a/config/config.go b/config/config.go index 609fa9e6a13..7e902df2133 100644 --- a/config/config.go +++ b/config/config.go @@ -801,7 +801,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.huaweiads.disabled", true) 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://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04") + v.SetDefault("adapters.interactiveoffers.endpoint", "https://prebid-server.ioadx.com/bidRequest/?partnerId={{.AccountID}}") v.SetDefault("adapters.ix.disabled", true) v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost") v.SetDefault("adapters.kayzen.endpoint", "https://bids-{{.ZoneID}}.bidder.kayzen.io/?exchange={{.AccountID}}") diff --git a/openrtb_ext/imp_interactiveoffers.go b/openrtb_ext/imp_interactiveoffers.go index 2f8fdf0324e..4332cc63074 100644 --- a/openrtb_ext/imp_interactiveoffers.go +++ b/openrtb_ext/imp_interactiveoffers.go @@ -1,5 +1,5 @@ package openrtb_ext type ExtImpInteractiveoffers struct { - PubID int `json:"pubid"` + PartnerId string `json:"partnerId"` } diff --git a/static/bidder-info/interactiveoffers.yaml b/static/bidder-info/interactiveoffers.yaml index 9a36076bab9..70e810f5fda 100644 --- a/static/bidder-info/interactiveoffers.yaml +++ b/static/bidder-info/interactiveoffers.yaml @@ -1,5 +1,5 @@ maintainer: - email: "support@interactiveoffers.com" + email: "dev@interactiveoffers.com" capabilities: app: mediaTypes: diff --git a/static/bidder-params/interactiveoffers.json b/static/bidder-params/interactiveoffers.json index 79338dcc40a..1bc3c1c048e 100644 --- a/static/bidder-params/interactiveoffers.json +++ b/static/bidder-params/interactiveoffers.json @@ -4,10 +4,10 @@ "description": "A schema which validates params accepted by Interactive Offers adapter", "type": "object", "properties": { - "pubid": { - "type": "integer", - "description": "The publisher id" + "partnerId": { + "type": "string", + "description": "The partners id" } }, - "required": ["pubid"] + "required": [] } \ No newline at end of file From dbdb311826278508b16aaf45e974a1678f22a96b Mon Sep 17 00:00:00 2001 From: Gena Date: Thu, 2 Sep 2021 21:23:06 +0300 Subject: [PATCH 076/140] New Adapter: OpenWeb (#1927) Co-authored-by: Eugene Fedorenko --- adapters/openweb/openweb.go | 191 ++++++++++++++++++ adapters/openweb/openweb_test.go | 20 ++ .../exemplary/multiple-imps-same-aid.json | 174 ++++++++++++++++ .../openwebtest/exemplary/simple-banner.json | 188 +++++++++++++++++ .../openwebtest/exemplary/simple-video.json | 88 ++++++++ .../supplemental/no-valid-imps.json | 30 +++ .../openwebtest/supplemental/status-204.json | 55 +++++ .../openwebtest/supplemental/status-400.json | 65 ++++++ .../openwebtest/supplemental/status-500.json | 65 ++++++ .../wrong-impression-mapping.json | 77 +++++++ adapters/openweb/params_test.go | 60 ++++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + go.mod | 1 - go.sum | 2 - openrtb_ext/bidders.go | 2 + openrtb_ext/imp_openweb.go | 9 + static/bidder-info/openweb.yaml | 17 ++ static/bidder-params/openweb.json | 26 +++ 19 files changed, 1070 insertions(+), 3 deletions(-) create mode 100644 adapters/openweb/openweb.go create mode 100644 adapters/openweb/openweb_test.go create mode 100644 adapters/openweb/openwebtest/exemplary/multiple-imps-same-aid.json create mode 100644 adapters/openweb/openwebtest/exemplary/simple-banner.json create mode 100644 adapters/openweb/openwebtest/exemplary/simple-video.json create mode 100644 adapters/openweb/openwebtest/supplemental/no-valid-imps.json create mode 100644 adapters/openweb/openwebtest/supplemental/status-204.json create mode 100644 adapters/openweb/openwebtest/supplemental/status-400.json create mode 100644 adapters/openweb/openwebtest/supplemental/status-500.json create mode 100644 adapters/openweb/openwebtest/supplemental/wrong-impression-mapping.json create mode 100644 adapters/openweb/params_test.go create mode 100644 openrtb_ext/imp_openweb.go create mode 100644 static/bidder-info/openweb.yaml create mode 100644 static/bidder-params/openweb.json diff --git a/adapters/openweb/openweb.go b/adapters/openweb/openweb.go new file mode 100644 index 00000000000..9c6c48c586b --- /dev/null +++ b/adapters/openweb/openweb.go @@ -0,0 +1,191 @@ +package openweb + +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 +} + +type openwebImpExt struct { + OpenWeb openrtb_ext.ExtImpOpenWeb `json:"openweb"` +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + totalImps := len(request.Imp) + errors := make([]error, 0, totalImps) + sourceIdToImpIds := make(map[int][]int) + var sourceIds []int + + for i := 0; i < totalImps; i++ { + + sourceId, err := validateImpression(&request.Imp[i]) + + if err != nil { + errors = append(errors, err) + continue + } + + if _, ok := sourceIdToImpIds[sourceId]; !ok { + sourceIdToImpIds[sourceId] = make([]int, 0, totalImps-i) + sourceIds = append(sourceIds, sourceId) + } + + sourceIdToImpIds[sourceId] = append(sourceIdToImpIds[sourceId], i) + + } + + totalReqs := len(sourceIdToImpIds) + if totalReqs == 0 { + return nil, errors + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + reqs := make([]*adapters.RequestData, 0, totalReqs) + + imps := request.Imp + reqCopy := *request + reqCopy.Imp = make([]openrtb2.Imp, totalImps) + for _, sourceId := range sourceIds { + impIds := sourceIdToImpIds[sourceId] + reqCopy.Imp = reqCopy.Imp[:0] + + for i := 0; i < len(impIds); i++ { + reqCopy.Imp = append(reqCopy.Imp, imps[impIds[i]]) + } + + body, err := json.Marshal(reqCopy) + if err != nil { + errors = append(errors, fmt.Errorf("error while encoding bidRequest, err: %s", err)) + return nil, errors + } + + reqs = append(reqs, &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint + fmt.Sprintf("?aid=%d", sourceId), + Body: body, + Headers: headers, + }) + } + + return reqs, errors + +} + +func (a *adapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if httpRes.StatusCode == http.StatusNoContent { + return nil, nil + } + + if httpRes.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Remote server error: %s", httpRes.Body), + }} + } + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("error while decoding response, err: %s", err), + }} + } + + bidResponse := adapters.NewBidderResponse() + var errors []error + + for _, sb := range bidResp.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + + bid := sb.Bid[i] + + mediaType, impOK := getBidType(bidReq.Imp, bid.ImpID) + if !impOK { + errors = append(errors, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("ignoring bid id=%s, request doesn't contain any impression with id=%s", bid.ID, bid.ImpID), + }) + continue + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: mediaType, + }) + } + } + + return bidResponse, errors +} + +func getBidType(imps []openrtb2.Imp, impId string) (mediaType openrtb_ext.BidType, ok bool) { + mediaType = openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impId { + ok = true + + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + + break + } + } + + return +} + +func validateImpression(imp *openrtb2.Imp) (int, error) { + var bidderExt adapters.ExtImpBidder + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), + } + } + + impExt := openrtb_ext.ExtImpOpenWeb{} + err := json.Unmarshal(bidderExt.Bidder, &impExt) + if err != nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), + } + } + + var impExtBuffer []byte + + impExtBuffer, err = json.Marshal(&openwebImpExt{ + OpenWeb: impExt, + }) + if err != nil { + return 0, &errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, error while encoding impExt, err: %s", imp.ID, err), + } + } + + if impExt.BidFloor > 0 { + imp.BidFloor = impExt.BidFloor + } + + imp.Ext = impExtBuffer + + return impExt.SourceID, nil +} + +// Builder builds a new instance of the OpenWeb 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/openweb/openweb_test.go b/adapters/openweb/openweb_test.go new file mode 100644 index 00000000000..007bcf524f1 --- /dev/null +++ b/adapters/openweb/openweb_test.go @@ -0,0 +1,20 @@ +package openweb + +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.BidderOpenWeb, config.Adapter{ + Endpoint: "http://ghb.spotim.market/pbs/ortb"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "openwebtest", bidder) +} diff --git a/adapters/openweb/openwebtest/exemplary/multiple-imps-same-aid.json b/adapters/openweb/openwebtest/exemplary/multiple-imps-same-aid.json new file mode 100644 index 00000000000..2abc32695bc --- /dev/null +++ b/adapters/openweb/openwebtest/exemplary/multiple-imps-same-aid.json @@ -0,0 +1,174 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 30, + "ext": { + "bidder": { + "aid": 1000, + "siteId": 1234, + "bidFloor": 20 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 30, + "ext": { + "bidder": { + "aid": 1000, + "siteId": 4321, + "bidFloor": 20 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.spotim.market/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 20, + "ext": { + "openweb": { + "aid": 1000, + "siteId": 1234, + "bidFloor": 20 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 20, + "ext": { + "openweb": { + "aid": 1000, + "siteId": 4321, + "bidFloor": 20 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "test-seat", + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 30, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + }, + { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 30, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 30, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 30, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/openweb/openwebtest/exemplary/simple-banner.json b/adapters/openweb/openwebtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..dc4c42ac65c --- /dev/null +++ b/adapters/openweb/openwebtest/exemplary/simple-banner.json @@ -0,0 +1,188 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 30, + "ext": { + "bidder": { + "aid": 1000, + "siteId": 1234, + "bidFloor": 20 + } + } + }, + { + "id": "test-imp-id-2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 30, + "ext": { + "bidder": { + "aid": 2000, + "siteId": 4321, + "bidFloor": 20 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.spotim.market/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [ + {"w":300,"h":250}, + {"w":300,"h":600} + ] + }, + "bidfloor": 20, + "ext": { + "openweb": { + "aid": 1000, + "siteId": 1234, + "bidFloor": 20 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "test-seat", + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 30, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + }] + } + ], + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "http://ghb.spotim.market/pbs/ortb?aid=2000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id-2", + "banner": { + "format": [ + {"w":300,"h":250}, + {"w":300,"h":600} + ] + }, + "bidfloor": 20, + "ext": { + "openweb": { + "aid": 2000, + "siteId": 4321, + "bidFloor": 20 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "test-seat-2", + "bid": [{ + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 30, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + }] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 30, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 30, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/openweb/openwebtest/exemplary/simple-video.json b/adapters/openweb/openwebtest/exemplary/simple-video.json new file mode 100644 index 00000000000..795938a9ad8 --- /dev/null +++ b/adapters/openweb/openwebtest/exemplary/simple-video.json @@ -0,0 +1,88 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "aid": 1000 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.spotim.market/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "openweb": { + "aid": 1000 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 3.5, + "w": 900, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 3.5, + "w": 900, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/openweb/openwebtest/supplemental/no-valid-imps.json b/adapters/openweb/openwebtest/supplemental/no-valid-imps.json new file mode 100644 index 00000000000..3335639d59f --- /dev/null +++ b/adapters/openweb/openwebtest/supplemental/no-valid-imps.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "aid": "not valid value" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "^ignoring imp id=test-imp-id, error while decoding impExt", + "comparison": "regex" + } + ] +} diff --git a/adapters/openweb/openwebtest/supplemental/status-204.json b/adapters/openweb/openwebtest/supplemental/status-204.json new file mode 100644 index 00000000000..8c0274adda6 --- /dev/null +++ b/adapters/openweb/openwebtest/supplemental/status-204.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "aid": 1000 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.spotim.market/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "openweb": { + "aid": 1000 + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ] +} diff --git a/adapters/openweb/openwebtest/supplemental/status-400.json b/adapters/openweb/openwebtest/supplemental/status-400.json new file mode 100644 index 00000000000..1b8ea455483 --- /dev/null +++ b/adapters/openweb/openwebtest/supplemental/status-400.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "aid": 1000 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.spotim.market/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "openweb": { + "aid": 1000 + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": "Bad request" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Remote server error: \"Bad request\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/openweb/openwebtest/supplemental/status-500.json b/adapters/openweb/openwebtest/supplemental/status-500.json new file mode 100644 index 00000000000..1dac7250114 --- /dev/null +++ b/adapters/openweb/openwebtest/supplemental/status-500.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "aid": 1000 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.spotim.market/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "openweb": { + "aid": 1000 + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": "Internal error" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Remote server error: \"Internal error\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/openweb/openwebtest/supplemental/wrong-impression-mapping.json b/adapters/openweb/openwebtest/supplemental/wrong-impression-mapping.json new file mode 100644 index 00000000000..776cbabcb0c --- /dev/null +++ b/adapters/openweb/openwebtest/supplemental/wrong-impression-mapping.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "aid": 1000 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ghb.spotim.market/pbs/ortb?aid=1000", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "openweb": { + "aid": 1000 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test-bid-id", + "impid": "SOME-WRONG-IMP-ID", + "price": 3.5, + "w": 900, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "ignoring bid id=test-bid-id, request doesn't contain any impression with id=SOME-WRONG-IMP-ID", + "comparison": "literal" + } + ] +} diff --git a/adapters/openweb/params_test.go b/adapters/openweb/params_test.go new file mode 100644 index 00000000000..62bdaf9397e --- /dev/null +++ b/adapters/openweb/params_test.go @@ -0,0 +1,60 @@ +package openweb + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/openweb.json +// These also validate the format of the external API: request.imp[i].ext.openweb +// TestValidParams makes sure that the openweb 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.BidderOpenWeb, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected openweb params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the openweb 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.BidderOpenWeb, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"aid":123}`, + `{"aid":123,"placementId":1234}`, + `{"aid":123,"siteId":4321}`, + `{"aid":123,"siteId":0,"bidFloor":0}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"aid":"some string instead of int"}`, + `{"aid":"0"}`, + `{"aid":"123","placementId":"123"}`, + `{"aid":123, "placementId":"123", "siteId":"321"}`, +} diff --git a/config/config.go b/config/config.go index 7e902df2133..fe645b7ad09 100644 --- a/config/config.go +++ b/config/config.go @@ -824,6 +824,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}") 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.openweb.endpoint", "http://ghb.spotim.market/pbs/ortb") 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 74a1d257082..2a8497eb7dd 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -82,6 +82,7 @@ import ( "github.com/prebid/prebid-server/adapters/ninthdecimal" "github.com/prebid/prebid-server/adapters/nobid" "github.com/prebid/prebid-server/adapters/onetag" + "github.com/prebid/prebid-server/adapters/openweb" "github.com/prebid/prebid-server/adapters/openx" "github.com/prebid/prebid-server/adapters/operaads" "github.com/prebid/prebid-server/adapters/orbidder" @@ -213,6 +214,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, openrtb_ext.BidderNoBid: nobid.Builder, openrtb_ext.BidderOneTag: onetag.Builder, + openrtb_ext.BidderOpenWeb: openweb.Builder, openrtb_ext.BidderOpenx: openx.Builder, openrtb_ext.BidderOperaads: operaads.Builder, openrtb_ext.BidderOrbidder: orbidder.Builder, diff --git a/go.mod b/go.mod index 6c6f32090ba..d003abae24d 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,6 @@ require ( github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b 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.5 github.com/mattn/go-colorable v0.1.2 // indirect diff --git a/go.sum b/go.sum index 6db57ada702..343bcf0d8f8 100644 --- a/go.sum +++ b/go.sum @@ -205,8 +205,6 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4EI5MABq68= 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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index dd4c1c86ae8..8a43595f56c 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -155,6 +155,7 @@ const ( BidderNinthDecimal BidderName = "ninthdecimal" BidderNoBid BidderName = "nobid" BidderOneTag BidderName = "onetag" + BidderOpenWeb BidderName = "openweb" BidderOpenx BidderName = "openx" BidderOperaads BidderName = "operaads" BidderOrbidder BidderName = "orbidder" @@ -285,6 +286,7 @@ func CoreBidderNames() []BidderName { BidderNinthDecimal, BidderNoBid, BidderOneTag, + BidderOpenWeb, BidderOpenx, BidderOperaads, BidderOrbidder, diff --git a/openrtb_ext/imp_openweb.go b/openrtb_ext/imp_openweb.go new file mode 100644 index 00000000000..3d28265e698 --- /dev/null +++ b/openrtb_ext/imp_openweb.go @@ -0,0 +1,9 @@ +package openrtb_ext + +// ExtImpOpenWeb defines the contract for bidrequest.imp[i].ext.openweb +type ExtImpOpenWeb struct { + SourceID int `json:"aid"` + PlacementID int `json:"placementId,omitempty"` + SiteID int `json:"siteId,omitempty"` + BidFloor float64 `json:"bidFloor,omitempty"` +} diff --git a/static/bidder-info/openweb.yaml b/static/bidder-info/openweb.yaml new file mode 100644 index 00000000000..5886c4a7845 --- /dev/null +++ b/static/bidder-info/openweb.yaml @@ -0,0 +1,17 @@ +maintainer: + email: "monetization@openweb.com" +gvlVendorID: 280 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + # openweb supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-params/openweb.json b/static/bidder-params/openweb.json new file mode 100644 index 00000000000..ec5766ad663 --- /dev/null +++ b/static/bidder-params/openweb.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "OpenWeb Adapter Params", + "description": "A schema which validates params accepted by the OpenWeb 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"] +} From 0212c9ad02ce1a083ec19d831787050bd891c272 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 2 Sep 2021 12:00:35 -0700 Subject: [PATCH 077/140] processStoredRequests refactoring (#1961) --- endpoints/openrtb2/auction.go | 123 ++++++---- endpoints/openrtb2/auction_test.go | 226 ++++++++++-------- .../supplementary/imp-info-invalid.json | 23 ++ openrtb_ext/imp.go | 6 + 4 files changed, 223 insertions(+), 155 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-info-invalid.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index fea391918df..1d879443a5d 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -14,7 +14,7 @@ import ( "time" "github.com/buger/jsonparser" - jsonpatch "github.com/evanphx/json-patch" + "github.com/evanphx/json-patch" "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -262,8 +262,13 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() + impInfo, errs := parseImpInfo(requestJson) + if len(errs) > 0 { + return nil, nil, errs + } + // Fetch the Stored Request data and merge it into the HTTP request. - if requestJson, impExtInfoMap, errs = deps.processStoredRequests(ctx, requestJson); len(errs) > 0 { + if requestJson, impExtInfoMap, errs = deps.processStoredRequests(ctx, requestJson, impInfo); len(errs) > 0 { return } @@ -1317,23 +1322,32 @@ func getJsonSyntaxError(testJSON []byte) (bool, string) { return false, "" } -func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson []byte) ([]byte, map[string]exchange.ImpExtInfo, []error) { +func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson []byte, impInfo []ImpExtPrebidData) ([]byte, map[string]exchange.ImpExtInfo, []error) { // Parse the Stored Request IDs from the BidRequest and Imps. storedBidRequestId, hasStoredBidRequest, err := getStoredRequestId(requestJson) if err != nil { return nil, nil, []error{err} } - imps, impIds, idIndices, errs := parseImpInfo(requestJson) - if len(errs) > 0 { - return nil, nil, errs - } // Fetch the Stored Request data var storedReqIds []string if hasStoredBidRequest { storedReqIds = []string{storedBidRequestId} } - storedRequests, storedImps, errs := deps.storedReqFetcher.FetchRequests(ctx, storedReqIds, impIds) + + impStoredReqIds := make([]string, 0, len(impInfo)) + impStoredReqIdsUniqueTracker := make(map[string]struct{}, len(impInfo)) + for _, impData := range impInfo { + if impData.ImpExtPrebid.StoredRequest != nil && len(impData.ImpExtPrebid.StoredRequest.ID) > 0 { + storedImpId := impData.ImpExtPrebid.StoredRequest.ID + if _, present := impStoredReqIdsUniqueTracker[storedImpId]; !present { + impStoredReqIds = append(impStoredReqIds, storedImpId) + impStoredReqIdsUniqueTracker[storedImpId] = struct{}{} + } + } + } + + storedRequests, storedImps, errs := deps.storedReqFetcher.FetchRequests(ctx, storedReqIds, impStoredReqIds) if len(errs) != 0 { return nil, nil, errs } @@ -1378,42 +1392,44 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson // Apply any Stored Imps, if they exist. Since the JSON Merge Patch overrides arrays, // and Prebid Server defers to the HTTP Request to resolve conflicts, it's safe to // assume that the request.imp data did not change when applying the Stored BidRequest. - impExtInfoMap := make(map[string]exchange.ImpExtInfo, len(impIds)) - for i := 0; i < len(impIds); i++ { - resolvedImp, err := jsonpatch.MergePatch(storedImps[impIds[i]], imps[idIndices[i]]) + impExtInfoMap := make(map[string]exchange.ImpExtInfo, len(impInfo)) + resolvedImps := make([]json.RawMessage, 0, len(impInfo)) + for i, impData := range impInfo { + if impData.ImpExtPrebid.StoredRequest != nil && len(impData.ImpExtPrebid.StoredRequest.ID) > 0 { + resolvedImp, err := jsonpatch.MergePatch(storedImps[impData.ImpExtPrebid.StoredRequest.ID], impData.Imp) - if err != nil { - hasErr, Err := getJsonSyntaxError(imps[idIndices[i]]) - if hasErr { - err = fmt.Errorf("Invalid JSON in Imp[%d] of Incoming Request: %s", i, Err) - } else { - hasErr, Err = getJsonSyntaxError(storedImps[impIds[i]]) + if err != nil { + hasErr, errMessage := getJsonSyntaxError(impData.Imp) if hasErr { - err = fmt.Errorf("imp.ext.prebid.storedrequest.id %s: Stored Imp has Invalid JSON: %s", impIds[i], Err) + err = fmt.Errorf("Invalid JSON in Imp[%d] of Incoming Request: %s", i, errMessage) + } else { + hasErr, errMessage = getJsonSyntaxError(storedImps[impData.ImpExtPrebid.StoredRequest.ID]) + if hasErr { + err = fmt.Errorf("imp.ext.prebid.storedrequest.id %s: Stored Imp has Invalid JSON: %s", impData.ImpExtPrebid.StoredRequest.ID, errMessage) + } } + return nil, nil, []error{err} } - return nil, nil, []error{err} - } - imps[idIndices[i]] = resolvedImp + resolvedImps = append(resolvedImps, resolvedImp) - impId, err := jsonparser.GetString(resolvedImp, "id") - if err != nil { - return nil, nil, []error{err} - } - // This is substantially faster for reading values from a json blob than the Go json package, - // but keep in mind that each jsonparser.GetXXX call re-parses the entire json. - // Based on performance measurements, the tipping point of efficiency is around 4 calls. - // At that point, please consider switching to EachKey to use a single pass. - includeVideoAttributes, err := jsonparser.GetBoolean(resolvedImp, "ext", "prebid", "options", "echovideoattrs") - if err != nil && err != jsonparser.KeyPathNotFoundError { - return nil, nil, []error{err} - } - if storedImps[impIds[i]] != nil { - impExtInfoMap[impId] = exchange.ImpExtInfo{EchoVideoAttrs: includeVideoAttributes, StoredImp: storedImps[impIds[i]]} + impId, err := jsonparser.GetString(resolvedImp, "id") + if err != nil { + return nil, nil, []error{err} + } + + echoVideoAttributes := false + if impData.ImpExtPrebid.Options != nil { + echoVideoAttributes = impData.ImpExtPrebid.Options.EchoVideoAttrs + } + impExtInfoMap[impId] = exchange.ImpExtInfo{EchoVideoAttrs: echoVideoAttributes, StoredImp: storedImps[impData.ImpExtPrebid.StoredRequest.ID]} + + } else { + resolvedImps = append(resolvedImps, impData.Imp) } + } - if len(impIds) > 0 { - newImpJson, err := json.Marshal(imps) + if len(resolvedImps) > 0 { + newImpJson, err := json.Marshal(resolvedImps) if err != nil { return nil, nil, []error{err} } @@ -1426,29 +1442,30 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson return resolvedRequest, impExtInfoMap, nil } -// parseImpInfo parses the request JSON and returns several things about the Imps -// -// 1. A list of the JSON for every Imp. -// 2. A list of all IDs which appear at `imp[i].ext.prebid.storedrequest.id`. -// 3. A list intended to parallel "ids". Each element tells which index of "imp[index]" the corresponding element of "ids" should modify. -// 4. Any errors which occur due to bad requests. These should warrant an HTTP 4xx response. -func parseImpInfo(requestJson []byte) (imps []json.RawMessage, ids []string, impIdIndices []int, errs []error) { +// parseImpInfo parses the request JSON and returns impression and unmarshalled imp.ext.prebid +func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) { + if impArray, dataType, _, err := jsonparser.Get(requestJson, "imp"); err == nil && dataType == jsonparser.Array { - i := 0 - jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) { - if storedImpId, hasStoredImp, err := getStoredRequestId(imp); err != nil { - errs = append(errs, err) - } else if hasStoredImp { - ids = append(ids, storedImpId) - impIdIndices = append(impIdIndices, i) + _, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) { + impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") + var impExtPrebid openrtb_ext.ExtImpPrebid + if impExtData != nil { + if err := json.Unmarshal(impExtData, &impExtPrebid); err != nil { + errs = append(errs, err) + } } - imps = append(imps, imp) - i++ + newImpData := ImpExtPrebidData{imp, impExtPrebid} + impData = append(impData, newImpData) }) } return } +type ImpExtPrebidData struct { + Imp json.RawMessage + ImpExtPrebid openrtb_ext.ExtImpPrebid +} + // getStoredRequestId parses a Stored Request ID from some json, without doing a full (slow) unmarshal. // It returns the ID, true/false whether a stored request key existed, and an error if anything went wrong // (e.g. malformed json, id not a string, etc). diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 748107c8d44..31c3c10951f 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1000,6 +1000,97 @@ func TestRefererParsing(t *testing.T) { } } +func TestParseImpInfoSingleImpression(t *testing.T) { + + expectedRes := []ImpExtPrebidData{ + { + Imp: json.RawMessage(`{"video":{"h":300,"w":200},"ext": {"prebid": {"storedrequest": {"id": "1"},"options": {"echovideoattrs": true}}}}`), + ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "1"}, Options: &openrtb_ext.Options{EchoVideoAttrs: true}}, + }, + { + Imp: json.RawMessage(`{"id": "adUnit2","ext": {"prebid": {"storedrequest": {"id": "1"},"options": {"echovideoattrs": true}},"appnexus": {"placementId": "def","trafficSourceCode": "mysite.com","reserve": null},"rubicon": null}}`), + ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "1"}, Options: &openrtb_ext.Options{EchoVideoAttrs: true}}, + }, + { + Imp: json.RawMessage(`{"ext": {"prebid": {"storedrequest": {"id": "2"},"options": {"echovideoattrs": false}}}}`), + ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "2"}, Options: &openrtb_ext.Options{EchoVideoAttrs: false}}, + }, + { + //in this case impression doesn't have storedrequest so we don't expect any data about this imp will be returned + Imp: json.RawMessage(`{"id": "some-static-imp","video":{"mimes":["video/mp4"]},"ext": {"appnexus": {"placementId": "abc","position": "below"}}}`), + ImpExtPrebid: openrtb_ext.ExtImpPrebid{}, + }, + } + + for i, requestData := range testStoredRequests { + impInfo, errs := parseImpInfo([]byte(requestData)) + assert.Len(t, errs, 0, "No errors should be returned") + assert.JSONEq(t, string(expectedRes[i].Imp), string(impInfo[0].Imp), "Incorrect impression data") + assert.Equal(t, expectedRes[i].ImpExtPrebid, impInfo[0].ImpExtPrebid, "Incorrect impression ext prebid data") + + } +} + +func TestParseImpInfoMultipleImpressions(t *testing.T) { + + inputData := []byte(`{ + "id": "ThisID", + "imp": [ + { + "id": "imp1", + "ext": { + "prebid": { + "storedrequest": { + "id": "1" + }, + "options": { + "echovideoattrs": true + } + } + } + }, + { + "id": "imp2", + "ext": { + "prebid": { + "storedrequest": { + "id": "2" + }, + "options": { + "echovideoattrs": false + } + } + } + }, + { + "id": "imp3" + } + ] + }`) + + expectedRes := []ImpExtPrebidData{ + { + Imp: json.RawMessage(`{"id": "imp1","ext": {"prebid": {"storedrequest": {"id": "1"},"options": {"echovideoattrs": true}}}}`), + ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "1"}, Options: &openrtb_ext.Options{EchoVideoAttrs: true}}, + }, + { + Imp: json.RawMessage(`{"id": "imp2","ext": {"prebid": {"storedrequest": {"id": "2"},"options": {"echovideoattrs": false}}}}`), + ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "2"}, Options: &openrtb_ext.Options{EchoVideoAttrs: false}}, + }, + { + Imp: json.RawMessage(`{"id": "imp3"}`), + ImpExtPrebid: openrtb_ext.ExtImpPrebid{}, + }, + } + + impInfo, errs := parseImpInfo([]byte(inputData)) + assert.Len(t, errs, 0, "No errors should be returned") + for i, res := range expectedRes { + assert.JSONEq(t, string(res.Imp), string(impInfo[i].Imp), "Incorrect impression data") + assert.Equal(t, res.ImpExtPrebid, impInfo[i].ImpExtPrebid, "Incorrect impression ext prebid data") + } +} + // Test the stored request functionality func TestStoredRequests(t *testing.T) { deps := &endpointDeps{ @@ -1023,7 +1114,9 @@ func TestStoredRequests(t *testing.T) { testStoreVideoAttr := []bool{true, true, false, false} for i, requestData := range testStoredRequests { - newRequest, impExtInfoMap, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData)) + impInfo, errs := parseImpInfo([]byte(requestData)) + assert.Len(t, errs, 0, "No errors should be returned") + newRequest, impExtInfoMap, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData), impInfo) if len(errList) != 0 { for _, err := range errList { if err != nil { @@ -1047,40 +1140,6 @@ func TestStoredRequests(t *testing.T) { } } -func TestStoredRequestsVideoErrors(t *testing.T) { - deps := &endpointDeps{ - &nobidExchange{}, - newParamsValidator(t), - &mockStoredReqFetcher{}, - empty_fetcher.EmptyFetcher{}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - false, - []byte{}, - openrtb_ext.BuildBidderMap(), - nil, - nil, - hardcodedResponseIPValidator{response: true}, - } - - // tests processStoredRequests function behavior in parsing incorrect input related to echovideoattrs feature - // this test now has 2 scenarios: - // 1) expected value we get using jsonparser.GetBoolean is integer - // 2) imp id is not found using jsonparser.GetString - // this loop iterates over testStoredRequestsErrors where input json has incorrect set up - // testStoredRequestsErrorsResults variable contains error message for every iteration - - for i, requestData := range testStoredRequestsErrors { - _, _, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData)) - - assert.NotEmpty(t, errList, "processStoredRequests should return error") - assert.Contains(t, errList[0].Error(), testStoredRequestsErrorsResults[i], "Incorrect error") - } -} - // TestOversizedRequest makes sure we behave properly when the request size exceeds the configured max. func TestOversizedRequest(t *testing.T) { reqBody := validRequest(t, "site.json") @@ -2311,6 +2370,36 @@ func TestAuctionWarnings(t *testing.T) { assert.Equal(t, errortypes.InvalidPrivacyConsentWarningCode, actualWarning.WarningCode, "Warning code is incorrect") } +func TestParseRequestParseImpInfoError(t *testing.T) { + reqBody := validRequest(t, "imp-info-invalid.json") + deps := &endpointDeps{ + &warningsCheckExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(len(reqBody))}, + &metricsConfig.DummyMetricsEngine{}, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) + + resReq, impExtInfoMap, errL := deps.parseRequest(req) + + assert.Nil(t, resReq, "Result request should be nil due to incorrect imp") + assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp") + assert.Len(t, errL, 1, "One error should be returned") + assert.Contains(t, errL[0].Error(), "echovideoattrs of type bool", "Incorrect error message") +} + func TestValidateNativeContextTypes(t *testing.T) { impIndex := 4 @@ -3141,73 +3230,6 @@ var testStoredRequests = []string{ }`, } -var testStoredRequestsErrors = []string{ - `{ - "id": "ThisID", - "imp": [ - { - "video":{ - "h":300, - "w":200 - }, - "ext": { - "prebid": { - "storedrequest": { - "id": "1" - }, - "options": { - "echovideoattrs": 1 - } - } - } - } - ], - "ext": { - "prebid": { - "cache": { - "markup": 1 - }, - "targeting": { - } - } - } - }`, `{ - "id": "ThisID", - "imp": [ - { - "video":{ - "h":300, - "w":200 - }, - "ext": { - "prebid": { - "storedrequest": { - "id": "10" - }, - "options": { - "echovideoattrs": true - } - } - } - } - ], - "ext": { - "prebid": { - "cache": { - "markup": 1 - }, - "targeting": { - } - } - } - }`, -} - -var testStoredRequestsErrorsResults = []string{ - "Value is not a boolean: 1", - "Key path not found", -} - // The expected requests after stored request processing var testFinalRequests = []string{ `{ diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-info-invalid.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-info-invalid.json new file mode 100644 index 00000000000..190a7ad0d7d --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-info-invalid.json @@ -0,0 +1,23 @@ +{ + "description": "Impression with stored request has invalid data", + "mockBidRequest": { + "id": "ThisID", + "imp": [ + { + "id": "imp1", + "ext": { + "prebid": { + "storedrequest": { + "id": "1" + }, + "options": { + "echovideoattrs": "true" + } + } + } + } + ] + }, + "expectedBidResponse": {}, + "expectedReturnCode": 200 +} diff --git a/openrtb_ext/imp.go b/openrtb_ext/imp.go index 670087adaaa..b51f2cde9b9 100644 --- a/openrtb_ext/imp.go +++ b/openrtb_ext/imp.go @@ -14,9 +14,15 @@ type ExtImpPrebid struct { // Bidder is the preferred approach for providing paramters to be interepreted by the bidder's adapter. Bidder map[string]json.RawMessage `json:"bidder"` + + Options *Options `json:"options,omitempty"` } // ExtStoredRequest defines the contract for bidrequest.imp[i].ext.prebid.storedrequest type ExtStoredRequest struct { ID string `json:"id"` } + +type Options struct { + EchoVideoAttrs bool `json:"echovideoattrs"` +} From cff2b3fb1d4c123e7c38ab17644bcf8ec872cd64 Mon Sep 17 00:00:00 2001 From: supportAceex <89574021+supportAceex@users.noreply.github.com> Date: Thu, 2 Sep 2021 22:47:26 +0300 Subject: [PATCH 078/140] New Adapter: Aceex (#1979) * init aceex prebid-server adapter * remove gvlVendorID from config * fix review * fix unit tests --- adapters/aceex/aceex.go | 190 ++++++++++++++++++ adapters/aceex/aceex_test.go | 28 +++ .../aceex/aceextest/exemplary/banner-app.json | 155 ++++++++++++++ .../aceex/aceextest/exemplary/banner-web.json | 143 +++++++++++++ .../aceex/aceextest/exemplary/native-app.json | 152 ++++++++++++++ .../aceex/aceextest/exemplary/native-web.json | 139 +++++++++++++ .../aceex/aceextest/exemplary/video-app.json | 164 +++++++++++++++ .../aceex/aceextest/exemplary/video-web.json | 162 +++++++++++++++ .../supplemental/empty-seatbid-array.json | 136 +++++++++++++ .../invalid-aceex-ext-object.json | 29 +++ .../supplemental/invalid-response.json | 111 ++++++++++ .../supplemental/status-code-bad-request.json | 92 +++++++++ .../supplemental/status-code-no-content.json | 68 +++++++ .../supplemental/status-code-other-error.json | 78 +++++++ .../status-code-service-unavailable.json | 78 +++++++ adapters/aceex/params_test.go | 50 +++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_aceex.go | 5 + static/bidder-info/aceex.yaml | 13 ++ static/bidder-params/aceex.json | 14 ++ 22 files changed, 1812 insertions(+) create mode 100644 adapters/aceex/aceex.go create mode 100644 adapters/aceex/aceex_test.go create mode 100644 adapters/aceex/aceextest/exemplary/banner-app.json create mode 100644 adapters/aceex/aceextest/exemplary/banner-web.json create mode 100644 adapters/aceex/aceextest/exemplary/native-app.json create mode 100644 adapters/aceex/aceextest/exemplary/native-web.json create mode 100644 adapters/aceex/aceextest/exemplary/video-app.json create mode 100644 adapters/aceex/aceextest/exemplary/video-web.json create mode 100644 adapters/aceex/aceextest/supplemental/empty-seatbid-array.json create mode 100644 adapters/aceex/aceextest/supplemental/invalid-aceex-ext-object.json create mode 100644 adapters/aceex/aceextest/supplemental/invalid-response.json create mode 100644 adapters/aceex/aceextest/supplemental/status-code-bad-request.json create mode 100644 adapters/aceex/aceextest/supplemental/status-code-no-content.json create mode 100644 adapters/aceex/aceextest/supplemental/status-code-other-error.json create mode 100644 adapters/aceex/aceextest/supplemental/status-code-service-unavailable.json create mode 100644 adapters/aceex/params_test.go create mode 100644 openrtb_ext/imp_aceex.go create mode 100644 static/bidder-info/aceex.yaml create mode 100644 static/bidder-params/aceex.json diff --git a/adapters/aceex/aceex.go b/adapters/aceex/aceex.go new file mode 100644 index 00000000000..abf748a7ead --- /dev/null +++ b/adapters/aceex/aceex.go @@ -0,0 +1,190 @@ +package aceex + +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 Aceex 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 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.IPv6) > 0 { + headers.Add("X-Forwarded-For", request.Device.IPv6) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + + return headers +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + + var errors []error + + aceexExt, err := a.getImpressionExt(&openRTBRequest.Imp[0]) + if err != nil { + return nil, append(errors, err) + } + + url, err := a.buildEndpointURL(aceexExt) + if err != nil { + return nil, []error{err} + } + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: url, + Headers: getHeaders(openRTBRequest), + }}, nil +} + +func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtAceex, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "ext.bidder not provided", + } + } + var aceexExt openrtb_ext.ExtAceex + if err := json.Unmarshal(bidderExt.Bidder, &aceexExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "ext.bidder not provided", + } + } + imp.Ext = nil + return &aceexExt, nil +} + +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtAceex) (string, error) { + endpointParams := macros.EndpointTemplateParams{AccountID: params.AccountID} + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func (a *adapter) checkResponseStatusCodes(response *adapters.ResponseData) error { + if response.StatusCode == http.StatusBadRequest { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: [ %d ]", response.StatusCode), + } + } + + if response.StatusCode == http.StatusServiceUnavailable { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode), + } + } + + if response.StatusCode != http.StatusOK { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code: [ %d ]. Run with request.debug = 1 for more info", response.StatusCode), + } + } + + return 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 + } + + httpStatusError := a.checkResponseStatusCodes(bidderRawResponse) + if httpStatusError != nil { + return nil, []error{httpStatusError} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid array", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + sb := bidResp.SeatBid[0] + + for _, bid := range sb.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(bid.ImpID, openRTBRequest.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/aceex/aceex_test.go b/adapters/aceex/aceex_test.go new file mode 100644 index 00000000000..4970f652bd0 --- /dev/null +++ b/adapters/aceex/aceex_test.go @@ -0,0 +1,28 @@ +package aceex + +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.BidderAceex, config.Adapter{ + Endpoint: "http://us.example.com/bid?uqhash={{.AccountID}}"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "aceextest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAceex, config.Adapter{ + Endpoint: "{{Malformed}}"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/aceex/aceextest/exemplary/banner-app.json b/adapters/aceex/aceextest/exemplary/banner-app.json new file mode 100644 index 00000000000..f6509699ad0 --- /dev/null +++ b/adapters/aceex/aceextest/exemplary/banner-app.json @@ -0,0 +1,155 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "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" + ] + }, + "uri": "http://us.example.com/bid?uqhash=hash", + "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 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "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": "aceex" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "aceex": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/aceex/aceextest/exemplary/banner-web.json b/adapters/aceex/aceextest/exemplary/banner-web.json new file mode 100644 index 00000000000..cf0d69bed7f --- /dev/null +++ b/adapters/aceex/aceextest/exemplary/banner-web.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "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", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "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" + ] + }, + "uri": "http://us.example.com/bid?uqhash=hash", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + } + } + ], + "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": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + } + ], + "type": "banner", + "seat": "aceex" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "aceex": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "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/aceex/aceextest/exemplary/native-app.json b/adapters/aceex/aceextest/exemplary/native-app.json new file mode 100644 index 00000000000..3e8d0fe2b96 --- /dev/null +++ b/adapters/aceex/aceextest/exemplary/native-app.json @@ -0,0 +1,152 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "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": { + "accountid": "hash" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "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" + ] + }, + "uri": "http://us.example.com/bid?uqhash=hash", + "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}}}" + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "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" + } + ], + "type": "native", + "seat": "aceex" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "aceex": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ] + }, + "type": "native" + } + ] + } + ] + } + \ No newline at end of file diff --git a/adapters/aceex/aceextest/exemplary/native-web.json b/adapters/aceex/aceextest/exemplary/native-web.json new file mode 100644 index 00000000000..a0c220ef74c --- /dev/null +++ b/adapters/aceex/aceextest/exemplary/native-web.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "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", + "tagid": "ogTAGID", + "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": { + "host": "ep1", + "accountid": "hash" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "2607:fb90:f27:4512:d800:cb23:a603:e245" + ] + }, + "uri": "http://us.example.com/bid?uqhash=hash", + "body": { + "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", + "tagid": "ogTAGID", + "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": "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" + } + ], + "seat": "acuityads" + } + ], + "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" + ] + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/aceex/aceextest/exemplary/video-app.json b/adapters/aceex/aceextest/exemplary/video-app.json new file mode 100644 index 00000000000..cc27ad608f3 --- /dev/null +++ b/adapters/aceex/aceextest/exemplary/video-app.json @@ -0,0 +1,164 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "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" + ] + }, + "uri": "http://us.example.com/bid?uqhash=hash", + "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 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "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 + } + ], + "seat": "aceex" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "aceex": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "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/aceex/aceextest/exemplary/video-web.json b/adapters/aceex/aceextest/exemplary/video-web.json new file mode 100644 index 00000000000..a15590860c9 --- /dev/null +++ b/adapters/aceex/aceextest/exemplary/video-web.json @@ -0,0 +1,162 @@ +{ + "mockBidRequest": { + "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", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "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" + ] + }, + "uri": "http://us.example.com/bid?uqhash=hash", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "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": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "aceex" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "aceex": 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": { + "prebid": { + "type": "video" + } + } + }, + "type":"video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/aceex/aceextest/supplemental/empty-seatbid-array.json b/adapters/aceex/aceextest/supplemental/empty-seatbid-array.json new file mode 100644 index 00000000000..e53e6252135 --- /dev/null +++ b/adapters/aceex/aceextest/supplemental/empty-seatbid-array.json @@ -0,0 +1,136 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "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" + ] + }, + "uri": "http://us.example.com/bid?uqhash=hash", + "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 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "aceex": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "mockResponse": { + "status": 200, + "body": "invalid response" + }, + "expectedMakeBidsErrors": [ + { + "value": "Empty SeatBid array", + "comparison": "literal" + } + ] +} diff --git a/adapters/aceex/aceextest/supplemental/invalid-aceex-ext-object.json b/adapters/aceex/aceextest/supplemental/invalid-aceex-ext-object.json new file mode 100644 index 00000000000..77752d01edf --- /dev/null +++ b/adapters/aceex/aceextest/supplemental/invalid-aceex-ext-object.json @@ -0,0 +1,29 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "ext.bidder not provided", + "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": "Awesome" + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/aceex/aceextest/supplemental/invalid-response.json b/adapters/aceex/aceextest/supplemental/invalid-response.json new file mode 100644 index 00000000000..c1d62068089 --- /dev/null +++ b/adapters/aceex/aceextest/supplemental/invalid-response.json @@ -0,0 +1,111 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "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" + ] + }, + "uri": "http://us.example.com/bid?uqhash=hash", + "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 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": "invalid response" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Bad Server Response", + "comparison": "literal" + } + ] +} diff --git a/adapters/aceex/aceextest/supplemental/status-code-bad-request.json b/adapters/aceex/aceextest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..553a9264a10 --- /dev/null +++ b/adapters/aceex/aceextest/supplemental/status-code-bad-request.json @@ -0,0 +1,92 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://us.example.com/bid?uqhash=hash", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "tagid": "ogTAGID" + } + ], + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: [ 400 ]", + "comparison": "literal" + } + ] +} diff --git a/adapters/aceex/aceextest/supplemental/status-code-no-content.json b/adapters/aceex/aceextest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..c669c3ce450 --- /dev/null +++ b/adapters/aceex/aceextest/supplemental/status-code-no-content.json @@ -0,0 +1,68 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [{ + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://us.example.com/bid?uqhash=hash", + "body": { + "id": "some-request-id", + "imp": [{ + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + }], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 204 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/aceex/aceextest/supplemental/status-code-other-error.json b/adapters/aceex/aceextest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..18892fe5e71 --- /dev/null +++ b/adapters/aceex/aceextest/supplemental/status-code-other-error.json @@ -0,0 +1,78 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://us.example.com/bid?uqhash=hash", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "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/aceex/aceextest/supplemental/status-code-service-unavailable.json b/adapters/aceex/aceextest/supplemental/status-code-service-unavailable.json new file mode 100644 index 00000000000..9c244ce36d5 --- /dev/null +++ b/adapters/aceex/aceextest/supplemental/status-code-service-unavailable.json @@ -0,0 +1,78 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "accountid": "hash" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://us.example.com/bid?uqhash=hash", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "ogTAGID", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 503 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong, please contact your Account Manager. Status Code: [ 503 ] ", + "comparison": "literal" + } + ] +} diff --git a/adapters/aceex/params_test.go b/adapters/aceex/params_test.go new file mode 100644 index 00000000000..220adb23379 --- /dev/null +++ b/adapters/aceex/params_test.go @@ -0,0 +1,50 @@ +package aceex + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "accountid": "hash" }`, +} + +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.BidderAceex, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Aceex params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`, + `{ "accountid": "" }`, +} + +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.BidderAceex, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/config/config.go b/config/config.go index fe645b7ad09..20a53108193 100644 --- a/config/config.go +++ b/config/config.go @@ -738,6 +738,7 @@ func SetupViper(v *viper.Viper, filename string) { // for them and specify all the parameters they need for them to work correctly. v.SetDefault("adapters.33across.endpoint", "https://ssc.33across.com/api/v1/s2s") v.SetDefault("adapters.33across.partner_id", "") + v.SetDefault("adapters.aceex.endpoint", "http://bl-us.aceex.io/?uqhash={{.AccountID}}") 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 2a8497eb7dd..894b6754527 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -3,6 +3,7 @@ package exchange import ( "github.com/prebid/prebid-server/adapters" ttx "github.com/prebid/prebid-server/adapters/33across" + "github.com/prebid/prebid-server/adapters/aceex" "github.com/prebid/prebid-server/adapters/acuityads" "github.com/prebid/prebid-server/adapters/adagio" "github.com/prebid/prebid-server/adapters/adf" @@ -133,6 +134,7 @@ import ( func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { return map[openrtb_ext.BidderName]adapters.Builder{ openrtb_ext.Bidder33Across: ttx.Builder, + openrtb_ext.BidderAceex: aceex.Builder, openrtb_ext.BidderAcuityAds: acuityads.Builder, openrtb_ext.BidderAdagio: adagio.Builder, openrtb_ext.BidderAdf: adf.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 8a43595f56c..f03ce21c2dc 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -74,6 +74,7 @@ func IsBidderNameReserved(name string) bool { // Please keep this list alphabetized to minimize merge conflicts. const ( Bidder33Across BidderName = "33across" + BidderAceex BidderName = "aceex" BidderAcuityAds BidderName = "acuityads" BidderAdagio BidderName = "adagio" BidderAdf BidderName = "adf" @@ -205,6 +206,7 @@ const ( func CoreBidderNames() []BidderName { return []BidderName{ Bidder33Across, + BidderAceex, BidderAcuityAds, BidderAdagio, BidderAdf, diff --git a/openrtb_ext/imp_aceex.go b/openrtb_ext/imp_aceex.go new file mode 100644 index 00000000000..62500852ee2 --- /dev/null +++ b/openrtb_ext/imp_aceex.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtAceex struct { + AccountID string `json:"accountid"` +} diff --git a/static/bidder-info/aceex.yaml b/static/bidder-info/aceex.yaml new file mode 100644 index 00000000000..a94d37528d1 --- /dev/null +++ b/static/bidder-info/aceex.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "tech@aceex.io" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/aceex.json b/static/bidder-params/aceex.json new file mode 100644 index 00000000000..1539af91a56 --- /dev/null +++ b/static/bidder-params/aceex.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Aceex Adapter Params", + "description": "A schema which validates params accepted by the Aceex adapter", + "type": "object", + "properties": { + "accountid": { + "type": "string", + "description": "Account id", + "minLength": 1 + } + }, + "required": ["accountid"] +} \ No newline at end of file From 7908d2622c87a8a80e4e56ab1632f4ace095fa69 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Tue, 7 Sep 2021 14:07:52 -0400 Subject: [PATCH 079/140] Update Go-GDPR Version (#1984) --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index d003abae24d..347f36bcf86 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/copystructure v1.1.2 github.com/mxmCherry/openrtb/v15 v15.0.0 - github.com/prebid/go-gdpr v0.9.0 + github.com/prebid/go-gdpr v1.10.0 github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect diff --git a/go.sum b/go.sum index 343bcf0d8f8..31e6b8fc93f 100644 --- a/go.sum +++ b/go.sum @@ -57,7 +57,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456/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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -263,8 +262,8 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -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/prebid/go-gdpr v1.10.0 h1:f9Ua+PAIK97j82QkJtIsohlbyU8961mFphw23hTsoMo= +github.com/prebid/go-gdpr v1.10.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY= 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-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= From 29ff1543315beed95b658d12830b47a555072ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Tue, 7 Sep 2021 21:58:37 +0200 Subject: [PATCH 080/140] use Contains instead of ContainsAny to validate external cache host protocol (#1988) --- config/config.go | 2 +- config/config_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 20a53108193..2b493513880 100644 --- a/config/config.go +++ b/config/config.go @@ -148,7 +148,7 @@ func (data *ExternalCache) validate(errs []error) []error { if strings.HasSuffix(data.Host, "/") { return append(errs, errors.New(fmt.Sprintf("External cache Host '%s' must not end with a path separator", data.Host))) } - if strings.ContainsAny(data.Host, "://") { + if strings.Contains(data.Host, "://") { return append(errs, errors.New(fmt.Sprintf("External cache Host must not specify a protocol. '%s'", data.Host))) } if !strings.HasPrefix(data.Path, "/") { diff --git a/config/config_test.go b/config/config_test.go index 2e889a79d0f..b22b384d9bd 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -108,6 +108,11 @@ func TestExternalCacheURLValidate(t *testing.T) { data: ExternalCache{Scheme: "https", Host: "www.google.com", Path: "/path/v1"}, expErrors: 0, }, + { + desc: "Host with port", + data: ExternalCache{Scheme: "https", Host: "localhost:2424", Path: "/path/v1"}, + expErrors: 0, + }, } for _, test := range testCases { errs := test.data.validate([]error{}) From fd4f16cb24e0384f6c5336d59c259ec9b3f799a8 Mon Sep 17 00:00:00 2001 From: AdView Date: Wed, 8 Sep 2021 11:58:29 +0800 Subject: [PATCH 081/140] New Adapter: Adview (#1982) Co-authored-by: wildcat0601 --- adapters/adview/adview.go | 141 ++++++++++++++++ adapters/adview/adview_test.go | 28 ++++ .../exemplary/banner-app-format.json | 156 ++++++++++++++++++ .../adviewtest/exemplary/banner-app.json | 146 ++++++++++++++++ .../adviewtest/exemplary/native-app.json | 142 ++++++++++++++++ .../adviewtest/exemplary/video-app.json | 155 +++++++++++++++++ .../adviewtest/supplemental/bad-request.json | 50 ++++++ .../supplemental/empty-response.json | 44 +++++ .../supplemental/nobid-response.json | 51 ++++++ .../adviewtest/supplemental/server-error.json | 51 ++++++ .../supplemental/unparsable-response.json | 51 ++++++ adapters/adview/params_test.go | 48 ++++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adview.go | 6 + static/bidder-info/adview.yaml | 9 + static/bidder-params/adview.json | 19 +++ 18 files changed, 1102 insertions(+) create mode 100644 adapters/adview/adview.go create mode 100644 adapters/adview/adview_test.go create mode 100644 adapters/adview/adviewtest/exemplary/banner-app-format.json create mode 100644 adapters/adview/adviewtest/exemplary/banner-app.json create mode 100644 adapters/adview/adviewtest/exemplary/native-app.json create mode 100644 adapters/adview/adviewtest/exemplary/video-app.json create mode 100644 adapters/adview/adviewtest/supplemental/bad-request.json create mode 100644 adapters/adview/adviewtest/supplemental/empty-response.json create mode 100644 adapters/adview/adviewtest/supplemental/nobid-response.json create mode 100644 adapters/adview/adviewtest/supplemental/server-error.json create mode 100644 adapters/adview/adviewtest/supplemental/unparsable-response.json create mode 100644 adapters/adview/params_test.go create mode 100644 openrtb_ext/imp_adview.go create mode 100644 static/bidder-info/adview.yaml create mode 100644 static/bidder-params/adview.json diff --git a/adapters/adview/adview.go b/adapters/adview/adview.go new file mode 100644 index 00000000000..c3e78b4221c --- /dev/null +++ b/adapters/adview/adview.go @@ -0,0 +1,141 @@ +package adview + +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 adview adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + endpointTemplate, 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: endpointTemplate, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var bidderExt adapters.ExtImpBidder + imp := &request.Imp[0] + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("invalid imp.ext, %s", err.Error()), + }} + } + //use adview + var advImpExt openrtb_ext.ExtImpAdView + if err := json.Unmarshal(bidderExt.Bidder, &advImpExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("invalid bidderExt.Bidder, %s", err.Error()), + }} + } + + imp.TagID = advImpExt.MasterTagID //tagid means posid + //for adview bid request + if imp.Banner != nil { + if len(imp.Banner.Format) != 0 { + bannerCopy := *imp.Banner + bannerCopy.H = &imp.Banner.Format[0].H + bannerCopy.W = &imp.Banner.Format[0].W + imp.Banner = &bannerCopy + } + } + + url, err := a.buildEndpointURL(&advImpExt) + if err != nil { + return nil, []error{err} + } + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: url, + }}, 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.", + } + 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 +} + +// Builds endpoint url based on adapter-specific pub settings from imp.ext +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtImpAdView) (string, error) { + endpointParams := macros.EndpointTemplateParams{AccountID: params.AccountID} + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + 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, nil + } + } + return mediaType, nil +} diff --git a/adapters/adview/adview_test.go b/adapters/adview/adview_test.go new file mode 100644 index 00000000000..5b872581c9a --- /dev/null +++ b/adapters/adview/adview_test.go @@ -0,0 +1,28 @@ +package adview + +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.BidderAdView, config.Adapter{ + Endpoint: "https://bid.adview.com/agent/thirdAdxService/{{.AccountID}}"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adviewtest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderAdView, config.Adapter{ + Endpoint: "{{Malformed}}"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/adview/adviewtest/exemplary/banner-app-format.json b/adapters/adview/adviewtest/exemplary/banner-app-format.json new file mode 100644 index 00000000000..49956cdc519 --- /dev/null +++ b/adapters/adview/adviewtest/exemplary/banner-app-format.json @@ -0,0 +1,156 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "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": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50, + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "tagid": "posid00001", + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "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 + } + ], + "type": "banner", + "seat": "adview" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adview": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adview/adviewtest/exemplary/banner-app.json b/adapters/adview/adviewtest/exemplary/banner-app.json new file mode 100644 index 00000000000..e8697229f4a --- /dev/null +++ b/adapters/adview/adviewtest/exemplary/banner-app.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "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": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "tagid": "posid00001", + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "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 + } + ], + "type": "banner", + "seat": "adview" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adview": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adview/adviewtest/exemplary/native-app.json b/adapters/adview/adviewtest/exemplary/native-app.json new file mode 100644 index 00000000000..df776a36d30 --- /dev/null +++ b/adapters/adview/adviewtest/exemplary/native-app.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "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": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "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": { + "placementId": "posid00001", + "accountId": "accountid01" + } + }, + "tagid": "posid00001" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "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" + } + ], + "type": "native", + "seat": "adview" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adview": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "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/adview/adviewtest/exemplary/video-app.json b/adapters/adview/adviewtest/exemplary/video-app.json new file mode 100644 index 00000000000..2136a8f7f33 --- /dev/null +++ b/adapters/adview/adviewtest/exemplary/video-app.json @@ -0,0 +1,155 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "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": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "ifa":"00000000-0000-0000-0000-000000000000", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + }, + "tagid": "posid00001" + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "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 + } + ], + "seat": "adview" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adview": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "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/adview/adviewtest/supplemental/bad-request.json b/adapters/adview/adviewtest/supplemental/bad-request.json new file mode 100644 index 00000000000..131e6d830a2 --- /dev/null +++ b/adapters/adview/adviewtest/supplemental/bad-request.json @@ -0,0 +1,50 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "posid00001" + }] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher.", + "comparison": "literal" + } + ] +} diff --git a/adapters/adview/adviewtest/supplemental/empty-response.json b/adapters/adview/adviewtest/supplemental/empty-response.json new file mode 100644 index 00000000000..5bd86bc99a1 --- /dev/null +++ b/adapters/adview/adviewtest/supplemental/empty-response.json @@ -0,0 +1,44 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "posid00001" + }] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/adview/adviewtest/supplemental/nobid-response.json b/adapters/adview/adviewtest/supplemental/nobid-response.json new file mode 100644 index 00000000000..f2c8864d4b1 --- /dev/null +++ b/adapters/adview/adviewtest/supplemental/nobid-response.json @@ -0,0 +1,51 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "posid00001" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": null, + "bidid": null, + "cur": null + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/adview/adviewtest/supplemental/server-error.json b/adapters/adview/adviewtest/supplemental/server-error.json new file mode 100644 index 00000000000..7894be1b70a --- /dev/null +++ b/adapters/adview/adviewtest/supplemental/server-error.json @@ -0,0 +1,51 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "posid00001" + }] + } + }, + "mockResponse": { + "status": 500, + "body": "Server error" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/adview/adviewtest/supplemental/unparsable-response.json b/adapters/adview/adviewtest/supplemental/unparsable-response.json new file mode 100644 index 00000000000..cef70ba8771 --- /dev/null +++ b/adapters/adview/adviewtest/supplemental/unparsable-response.json @@ -0,0 +1,51 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.adview.com/agent/thirdAdxService/accountid01", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "placementId": "posid00001", + "accountId": "accountid01" + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "posid00001" + }] + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/adview/params_test.go b/adapters/adview/params_test.go new file mode 100644 index 00000000000..6d124e9b556 --- /dev/null +++ b/adapters/adview/params_test.go @@ -0,0 +1,48 @@ +package adview + +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.BidderAdView, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adview 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.BidderAdView, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{ "placementId": "posid00001", "accountId": "accountid01"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `{}`, + `{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`, + `{ "accountId": "accountid01" }`, + `{ "placementId": "posid00001" }`, + `{ "placementId": "", "accountId": "" }`, +} diff --git a/config/config.go b/config/config.go index 2b493513880..b29d09ae151 100644 --- a/config/config.go +++ b/config/config.go @@ -757,6 +757,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb") v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") + v.SetDefault("adapters.adview.endpoint", "https://bid.adview.com/agent/thirdAdxService/{{.AccountID}}") 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") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 894b6754527..630f18b6a3e 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -22,6 +22,7 @@ import ( "github.com/prebid/prebid-server/adapters/adtarget" "github.com/prebid/prebid-server/adapters/adtelligent" "github.com/prebid/prebid-server/adapters/advangelists" + "github.com/prebid/prebid-server/adapters/adview" "github.com/prebid/prebid-server/adapters/adxcg" "github.com/prebid/prebid-server/adapters/adyoulike" "github.com/prebid/prebid-server/adapters/aja" @@ -153,6 +154,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAdtarget: adtarget.Builder, openrtb_ext.BidderAdtelligent: adtelligent.Builder, openrtb_ext.BidderAdvangelists: advangelists.Builder, + openrtb_ext.BidderAdView: adview.Builder, openrtb_ext.BidderAdxcg: adxcg.Builder, openrtb_ext.BidderAdyoulike: adyoulike.Builder, openrtb_ext.BidderAJA: aja.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f03ce21c2dc..88f816c293c 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -93,6 +93,7 @@ const ( BidderAdtarget BidderName = "adtarget" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" + BidderAdView BidderName = "adview" BidderAdxcg BidderName = "adxcg" BidderAdyoulike BidderName = "adyoulike" BidderAJA BidderName = "aja" @@ -225,6 +226,7 @@ func CoreBidderNames() []BidderName { BidderAdtarget, BidderAdtelligent, BidderAdvangelists, + BidderAdView, BidderAdxcg, BidderAdyoulike, BidderAJA, diff --git a/openrtb_ext/imp_adview.go b/openrtb_ext/imp_adview.go new file mode 100644 index 00000000000..adaa59f36e7 --- /dev/null +++ b/openrtb_ext/imp_adview.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpAdView struct { + MasterTagID string `json:"placementId"` + AccountID string `json:"accountId"` +} diff --git a/static/bidder-info/adview.yaml b/static/bidder-info/adview.yaml new file mode 100644 index 00000000000..077bce62b45 --- /dev/null +++ b/static/bidder-info/adview.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "partner@adview.com" +gvlVendorID: 1022 +capabilities: + app: + mediaTypes: + - banner + - native + - video diff --git a/static/bidder-params/adview.json b/static/bidder-params/adview.json new file mode 100644 index 00000000000..b5c927aaeb2 --- /dev/null +++ b/static/bidder-params/adview.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AdView Adapter Params", + "description": "A schema which validates params accepted by the adview adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "description": "An ID which identifies the placement selling the impression", + "minLength": 1 + }, + "accountId": { + "type": "string", + "description": "Account id", + "minLength": 1 + } + }, + "required": ["placementId", "accountId"] +} From 46137c6e52c47e8982934320fa0eae0ff9f8a272 Mon Sep 17 00:00:00 2001 From: Michael Kuryshev Date: Wed, 8 Sep 2021 19:43:02 +0200 Subject: [PATCH 082/140] VIS.X: add video instream support (#1974) --- adapters/visx/visx.go | 30 ++- .../visxtest/exemplary/simple-banner.json | 6 - .../visx/visxtest/exemplary/simple-video.json | 173 ++++++++++++++++++ .../visxtest/supplemental/wrong_imp_type.json | 74 ++++++++ .../visxtest/supplemental/wrong_impid.json | 82 +++++++++ config/config.go | 2 +- static/bidder-info/visx.yaml | 2 + 7 files changed, 361 insertions(+), 8 deletions(-) create mode 100644 adapters/visx/visxtest/exemplary/simple-video.json create mode 100644 adapters/visx/visxtest/supplemental/wrong_imp_type.json create mode 100644 adapters/visx/visxtest/supplemental/wrong_impid.json diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index 8e2b10f7c32..f0b25b4a2a3 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -102,9 +102,14 @@ func (a *VisxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq bid.ADomain = sb.Bid[i].ADomain bid.DealID = sb.Bid[i].DealID + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + return nil, []error{err} + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bid, - BidType: "banner", + BidType: bidType, }) } } @@ -112,6 +117,29 @@ func (a *VisxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq } +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 + } + + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), + } + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), + } +} + // Builder builds a new instance of the Visx adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &VisxAdapter{ diff --git a/adapters/visx/visxtest/exemplary/simple-banner.json b/adapters/visx/visxtest/exemplary/simple-banner.json index 2840521c6eb..0f34e6f0325 100644 --- a/adapters/visx/visxtest/exemplary/simple-banner.json +++ b/adapters/visx/visxtest/exemplary/simple-banner.json @@ -22,9 +22,6 @@ "secure": 1, "ext": { "bidder": { - "ga_parms": { - "place_on_page": "ATF" - }, "size": [ 300, 250 @@ -89,9 +86,6 @@ "secure": 1, "ext": { "bidder": { - "ga_parms": { - "place_on_page": "ATF" - }, "size": [ 300, 250 diff --git a/adapters/visx/visxtest/exemplary/simple-video.json b/adapters/visx/visxtest/exemplary/simple-video.json new file mode 100644 index 00000000000..b39dad74a08 --- /dev/null +++ b/adapters/visx/visxtest/exemplary/simple-video.json @@ -0,0 +1,173 @@ +{ + "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", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "cur": ["USD"], + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": ["USD"], + "seatbid": [ + { + "bid": [ + { + "crid": "2_260", + "price": 0.500000, + "adm": "some-test-ad-vast", + "impid": "test-imp-id", + "auid": 11, + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "w": 300 + } + ], + "seat": "51" + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "price": 0.5, + "adm": "some-test-ad-vast", + "impid": "test-imp-id", + "id": "test-request-id", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "crid": "2_260", + "w": 300 + }, + "type": "video" + }] + }] +} diff --git a/adapters/visx/visxtest/supplemental/wrong_imp_type.json b/adapters/visx/visxtest/supplemental/wrong_imp_type.json new file mode 100644 index 00000000000..659a0204f6e --- /dev/null +++ b/adapters/visx/visxtest/supplemental/wrong_imp_type.json @@ -0,0 +1,74 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 7 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "cur": ["USD"], + "imp": [ + { + "id": "test-imp-id", + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 7 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": ["USD"], + "seatbid": [{ + "seat": "51", + "bid": [{ + "crid": "2_260", + "price": 0.500000, + "adm": "some-test-ad", + "impid": "test-imp-id", + "auid": 46, + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "w": 300 + }] + }] + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unknown impression type for ID: \"test-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/visx/visxtest/supplemental/wrong_impid.json b/adapters/visx/visxtest/supplemental/wrong_impid.json new file mode 100644 index 00000000000..ac18ac0719f --- /dev/null +++ b/adapters/visx/visxtest/supplemental/wrong_impid.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 7 + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "cur": ["USD"], + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "uid": 7 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": ["USD"], + "seatbid": [{ + "seat": "51", + "bid": [{ + "crid": "2_260", + "price": 0.500000, + "adm": "some-test-ad", + "impid": "unknown_impid", + "auid": 46, + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "w": 300 + }] + }] + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression for ID: \"unknown_impid\"", + "comparison": "literal" + } + ] +} diff --git a/config/config.go b/config/config.go index b29d09ae151..95f4f3e1d10 100644 --- a/config/config.go +++ b/config/config.go @@ -865,7 +865,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") 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.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard:0.1.0") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804") v.SetDefault("adapters.yeahmobi.endpoint", "https://{{.Host}}/prebid/bid") v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/") diff --git a/static/bidder-info/visx.yaml b/static/bidder-info/visx.yaml index 77d4463ac7a..7faf1b94ed9 100644 --- a/static/bidder-info/visx.yaml +++ b/static/bidder-info/visx.yaml @@ -5,9 +5,11 @@ capabilities: site: mediaTypes: - banner + - video app: mediaTypes: - banner + - video userSync: redirect: url: "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" From ca85729f81ff9ee262f7248f0f0d0e130b723412 Mon Sep 17 00:00:00 2001 From: Adprime <64427228+Adprime@users.noreply.github.com> Date: Wed, 8 Sep 2021 23:03:46 +0300 Subject: [PATCH 083/140] Adprime: added new bid params (#1963) * initial * initial * initial * use jsonparser * use jsonparser * log * log * log * log * log * log * add not ok status check * remove redundad struct * more fixes * add native * add keywords and odiences * fix * fix test * fix * fix Co-authored-by: Aiholkin --- adapters/adprime/adprime.go | 64 ++++- adapters/adprime/adprime_test.go | 2 +- .../adprimetest/exemplary/simple-banner.json | 246 +++++++++--------- .../adprimetest/exemplary/simple-native.json | 118 +++++++++ .../adprimetest/exemplary/simple-video.json | 228 ++++++++-------- .../exemplary/simple-web-banner.json | 229 ++++++++-------- .../adprimetest/exemplary/withAudiences.json | 138 ++++++++++ .../adprimetest/exemplary/withKeywords.json | 136 ++++++++++ .../adprimetest/params/race/banner.json | 3 + .../adprimetest/params/race/native.json | 3 + .../adprimetest/params/race/video.json | 3 + .../adprimetest/supplemental/bad-imp-ext.json | 72 ++--- .../supplemental/bad_media_type.json | 91 +++++++ .../supplemental/bad_response.json | 8 +- .../supplemental/no-imp-ext-1.json | 73 +++--- .../supplemental/no-imp-ext-2.json | 73 +++--- .../adprimetest/supplemental/status-204.json | 8 +- .../adprimetest/supplemental/status-400.json | 87 +++++++ .../adprimetest/supplemental/status-404.json | 8 +- adapters/adprime/params_test.go | 2 +- config/config.go | 2 +- openrtb_ext/imp_adprime.go | 4 +- static/bidder-info/adprime.yaml | 2 + static/bidder-params/adprime.json | 29 ++- 24 files changed, 1143 insertions(+), 486 deletions(-) create mode 100644 adapters/adprime/adprimetest/exemplary/simple-native.json create mode 100644 adapters/adprime/adprimetest/exemplary/withAudiences.json create mode 100644 adapters/adprime/adprimetest/exemplary/withKeywords.json create mode 100644 adapters/adprime/adprimetest/params/race/banner.json create mode 100644 adapters/adprime/adprimetest/params/race/native.json create mode 100644 adapters/adprime/adprimetest/params/race/video.json create mode 100644 adapters/adprime/adprimetest/supplemental/bad_media_type.json create mode 100644 adapters/adprime/adprimetest/supplemental/status-400.json diff --git a/adapters/adprime/adprime.go b/adapters/adprime/adprime.go index 70fbd6b399b..c907380db8d 100644 --- a/adapters/adprime/adprime.go +++ b/adapters/adprime/adprime.go @@ -4,8 +4,8 @@ import ( "encoding/json" "fmt" "net/http" + "strings" - "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" @@ -30,22 +30,63 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters func (a *AdprimeAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var err error - var tagID string var adapterRequests []*adapters.RequestData + var bidderExt adapters.ExtImpBidder + var adprimeExt openrtb_ext.ExtImpAdprime + reqCopy := *request for _, imp := range request.Imp { reqCopy.Imp = []openrtb2.Imp{imp} - tagID, err = jsonparser.GetString(reqCopy.Imp[0].Ext, "bidder", "TagID") + err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + err = json.Unmarshal(bidderExt.Bidder, &adprimeExt) if err != nil { errs = append(errs, err) - continue + return nil, errs } + // tagId + tagID := adprimeExt.TagID reqCopy.Imp[0].TagID = tagID + // placementId + newExt, err := json.Marshal( + map[string]interface{}{ + "bidder": map[string]interface{}{ + "TagID": tagID, + "placementId": tagID, + }, + }) + if err != nil { + errs = append(errs, err) + return nil, errs + } + reqCopy.Imp[0].Ext = newExt + + // keywords + if reqCopy.Site != nil && len(adprimeExt.Keywords) > 0 { + siteCopy := *reqCopy.Site + siteCopy.Keywords = strings.Join(adprimeExt.Keywords, ",") + reqCopy.Site = &siteCopy + } + + // audiences + if reqCopy.Site != nil && len(adprimeExt.Audiences) > 0 { + if reqCopy.User == nil { + reqCopy.User = &openrtb2.User{} + } + userCopy := *reqCopy.User + userCopy.CustomData = strings.Join(adprimeExt.Audiences, ",") + reqCopy.User = &userCopy + } + adapterReq, errors := a.makeRequest(&reqCopy) if adapterReq != nil { adapterRequests = append(adapterRequests, adapterReq) @@ -123,18 +164,21 @@ func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external } func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { - if imp.Banner == nil && imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil } - return mediaType, nil } } - // This shouldnt happen. Lets handle it just incase by returning an error. return "", &errortypes.BadInput{ - Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), + Message: fmt.Sprintf("Failed to find impression \"%s\"", impID), } } diff --git a/adapters/adprime/adprime_test.go b/adapters/adprime/adprime_test.go index ff12381d053..113233ee507 100644 --- a/adapters/adprime/adprime_test.go +++ b/adapters/adprime/adprime_test.go @@ -10,7 +10,7 @@ import ( func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdprime, config.Adapter{ - Endpoint: "http://delta.adprime.com/?c=o&m=ortb"}) + Endpoint: "http://delta.adprime.com/pserver"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) diff --git a/adapters/adprime/adprimetest/exemplary/simple-banner.json b/adapters/adprime/adprimetest/exemplary/simple-banner.json index 076175c6274..e96abe5cff1 100644 --- a/adapters/adprime/adprimetest/exemplary/simple-banner.json +++ b/adapters/adprime/adprimetest/exemplary/simple-banner.json @@ -1,134 +1,134 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "TagID": "test" + } + } + } + ] + }, + "httpCalls": [ { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 + "expectedRequest": { + "uri": "http://delta.adprime.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "TagID": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" }, - { - "w": 300, - "h": 600 + "device": { + "ip": "123.123.123.123", + "ua": "iPad" } - ] + } }, - "tagid": "1", - "ext": { - "bidder": { - "TagID": "1" + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" } } } ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } -}, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://delta.adprime.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "1", - "ext": { - "bidder": { - "TagID": "1" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "w": 300, - "h": 250, - "ext": { - "prebid": { - "type": "banner" - } - } - } - ], - "seat": "adprime" - } - ], - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "bids":[ + "expectedBidResponses": [ { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "w": 300, - "h": 250, - "ext": { - "prebid": { - "type": "banner" - } - } - }, - "type": "banner" + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] } - ] - } - ] -} + ] +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/exemplary/simple-native.json b/adapters/adprime/adprimetest/exemplary/simple-native.json new file mode 100644 index 00000000000..0ea8a0a1e3f --- /dev/null +++ b/adapters/adprime/adprimetest/exemplary/simple-native.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "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": { + "placementId": "test", + "TagID": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://delta.adprime.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "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": { + "placementId": "test", + "TagID": "test" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/exemplary/simple-video.json b/adapters/adprime/adprimetest/exemplary/simple-video.json index 3e61c4dddd1..164e3040b1d 100644 --- a/adapters/adprime/adprimetest/exemplary/simple-video.json +++ b/adapters/adprime/adprimetest/exemplary/simple-video.json @@ -1,119 +1,129 @@ { - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "ext": { - "bidder": { - "TagID": "288" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://delta.adprime.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "device": { + "mockBidRequest": { + "id": "test-request-id", + "device": { "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "app": { + "ua": "iPad" + }, + "app": { "id": "1", "bundle": "com.wls.testwlsapplication" - }, - "imp": [ + }, + "imp": [ { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "tagid": "288", - "ext": { - "bidder": { - "TagID": "288" + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test", + "TagID": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://delta.adprime.com/pserver", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "placementId": "test", + "TagID": "test" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" } - } } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "00:01:00", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "adprime" - } - ], - "cur": "USD" } - } - } - ], - - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ + ], + "expectedBidResponses": [ { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "00:01:00", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "00:01:00", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] } - ] - } - ] -} + ] +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/exemplary/simple-web-banner.json b/adapters/adprime/adprimetest/exemplary/simple-web-banner.json index a955854fb31..74797b199b6 100644 --- a/adapters/adprime/adprimetest/exemplary/simple-web-banner.json +++ b/adapters/adprime/adprimetest/exemplary/simple-web-banner.json @@ -1,133 +1,134 @@ { "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "1", - "ext": { - "bidder": { - "TagID": "1" - } - } - } - ], - "site": { - "id": "1", - "domain": "test.com" - }, - "device": { - "ip": "123.123.123.123" - } - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://delta.adprime.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "1", - "ext": { - "bidder": { - "TagID": "1" + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 } + ] + }, + "ext": { + "bidder": { + "TagID": "test", + "placementId": "test" } } - ], - "site": { - "id": "1", - "domain": "test.com" - }, - "device": { - "ip": "123.123.123.123" } - } + ], + "site": { + "id": "1", + "domain": "test.com" }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "bid": [ + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://delta.adprime.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "w": 468, - "h": 60, + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, "ext": { - "prebid": { - "type": "banner" + "bidder": { + "placementId": "test", + "TagID": "test" } } } ], - "seat": "adprime" + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } } - ], - "cur": "USD" - } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" + } + } } - } ], - "expectedBidResponses": [ - { - "bids":[ - { - "bid": { - "id": "test_bid_id", - "impid": "test-imp-id", - "price": 0.27543, - "adm": "", - "cid": "test_cid", - "crid": "test_crid", - "dealid": "test_dealid", - "w": 468, - "h": 60, - "ext": { - "prebid": { - "type": "banner" - } + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" } - }, - "type": "banner" - } - ] - } + ] + } ] - } - \ No newline at end of file +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/exemplary/withAudiences.json b/adapters/adprime/adprimetest/exemplary/withAudiences.json new file mode 100644 index 00000000000..ed8e1c003b3 --- /dev/null +++ b/adapters/adprime/adprimetest/exemplary/withAudiences.json @@ -0,0 +1,138 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "site": { + "id": "1", + "domain": "test.com" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "TagID": "test", + "audiences": ["aud1", "aud2"] + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://delta.adprime.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "TagID": "test" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "user": { + "customdata": "aud1,aud2" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/exemplary/withKeywords.json b/adapters/adprime/adprimetest/exemplary/withKeywords.json new file mode 100644 index 00000000000..3e1528fe78e --- /dev/null +++ b/adapters/adprime/adprimetest/exemplary/withKeywords.json @@ -0,0 +1,136 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "site": { + "id": "1", + "domain": "test.com" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "TagID": "test", + "keywords": ["keyw1", "keyw2"] + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://delta.adprime.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "placementId": "test", + "TagID": "test" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com", + "keywords": "keyw1,keyw2" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/race/banner.json b/adapters/adprime/adprimetest/params/race/banner.json new file mode 100644 index 00000000000..22651f516d6 --- /dev/null +++ b/adapters/adprime/adprimetest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "TagID": "test" +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/race/native.json b/adapters/adprime/adprimetest/params/race/native.json new file mode 100644 index 00000000000..22651f516d6 --- /dev/null +++ b/adapters/adprime/adprimetest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "TagID": "test" +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/params/race/video.json b/adapters/adprime/adprimetest/params/race/video.json new file mode 100644 index 00000000000..22651f516d6 --- /dev/null +++ b/adapters/adprime/adprimetest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "TagID": "test" +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json b/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json index a95c56e8426..39cd3cd02ce 100644 --- a/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json +++ b/adapters/adprime/adprimetest/supplemental/bad-imp-ext.json @@ -1,42 +1,42 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": { + "adprime": { + "TagID": "1" } - ] - }, - "tagid": "1", - "ext": { - "adprime": { - "TagID": "1" } } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } -}, -"expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } -] -} + "expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/bad_media_type.json b/adapters/adprime/adprimetest/supplemental/bad_media_type.json new file mode 100644 index 00000000000..e91a0138449 --- /dev/null +++ b/adapters/adprime/adprimetest/supplemental/bad_media_type.json @@ -0,0 +1,91 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "ext": { + "bidder": { + "TagID": "test", + "placementId": "test" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://delta.adprime.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "ext": { + "bidder": { + "placementId": "test", + "TagID": "test" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123", + "ua": "Ubuntu" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adprime" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression \"test-imp-id\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/bad_response.json b/adapters/adprime/adprimetest/supplemental/bad_response.json index 435529a485c..acec885f98a 100644 --- a/adapters/adprime/adprimetest/supplemental/bad_response.json +++ b/adapters/adprime/adprimetest/supplemental/bad_response.json @@ -19,7 +19,8 @@ "tagid": "17", "ext": { "bidder": { - "TagID": "17" + "TagID": "17", + "placementId": "17" } } } @@ -35,7 +36,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://delta.adprime.com/?c=o&m=ortb", + "uri": "http://delta.adprime.com/pserver", "body": { "id": "test-request-id", "imp": [ @@ -56,7 +57,8 @@ "tagid": "17", "ext": { "bidder": { - "TagID": "17" + "TagID": "17", + "placementId": "17" } } } diff --git a/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json b/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json index 1e38dbe4541..5ba65925a70 100644 --- a/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json +++ b/adapters/adprime/adprimetest/supplemental/no-imp-ext-1.json @@ -1,39 +1,38 @@ { "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "1", - "ext": "" - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] - } - \ No newline at end of file + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": "" + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json b/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json index f9759fae8ff..e08f5385b88 100644 --- a/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json +++ b/adapters/adprime/adprimetest/supplemental/no-imp-ext-2.json @@ -1,39 +1,38 @@ { "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "1", - "ext": {} - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] - } - \ No newline at end of file + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "1", + "ext": {} + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adprime/adprimetest/supplemental/status-204.json b/adapters/adprime/adprimetest/supplemental/status-204.json index 4fa57dcee86..c0c2f346fb7 100644 --- a/adapters/adprime/adprimetest/supplemental/status-204.json +++ b/adapters/adprime/adprimetest/supplemental/status-204.json @@ -19,7 +19,8 @@ "tagid": "17", "ext": { "bidder": { - "TagID": "17" + "TagID": "17", + "placementId": "17" } } } @@ -35,7 +36,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://delta.adprime.com/?c=o&m=ortb", + "uri": "http://delta.adprime.com/pserver", "body": { "id": "test-request-id", "imp": [ @@ -56,7 +57,8 @@ "tagid": "17", "ext": { "bidder": { - "TagID": "17" + "TagID": "17", + "placementId": "17" } } } diff --git a/adapters/adprime/adprimetest/supplemental/status-400.json b/adapters/adprime/adprimetest/supplemental/status-400.json new file mode 100644 index 00000000000..ab1c38a0346 --- /dev/null +++ b/adapters/adprime/adprimetest/supplemental/status-400.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000", + "placementId": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "http://delta.adprime.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "100000000", + "ext": { + "bidder": { + "TagID": "100000000", + "placementId": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adprime/adprimetest/supplemental/status-404.json b/adapters/adprime/adprimetest/supplemental/status-404.json index c2b303f0cb4..6cd718548d3 100644 --- a/adapters/adprime/adprimetest/supplemental/status-404.json +++ b/adapters/adprime/adprimetest/supplemental/status-404.json @@ -19,7 +19,8 @@ "tagid": "100000000", "ext": { "bidder": { - "TagID": "100000000" + "TagID": "100000000", + "placementId": "100000000" } } } @@ -35,7 +36,7 @@ }, "httpCalls": [{ "expectedRequest": { - "uri": "http://delta.adprime.com/?c=o&m=ortb", + "uri": "http://delta.adprime.com/pserver", "body": { "id": "test-request-id", "imp": [ @@ -56,7 +57,8 @@ "tagid": "100000000", "ext": { "bidder": { - "TagID": "100000000" + "TagID": "100000000", + "placementId": "100000000" } } } diff --git a/adapters/adprime/params_test.go b/adapters/adprime/params_test.go index 05adad5c4ff..b466c658ede 100644 --- a/adapters/adprime/params_test.go +++ b/adapters/adprime/params_test.go @@ -36,7 +36,7 @@ func TestInvalidParams(t *testing.T) { } var validParams = []string{ - `{"TagID": "1"}`, + `{"TagID": "test"}`, } var invalidParams = []string{ diff --git a/config/config.go b/config/config.go index 95f4f3e1d10..0286744df4a 100644 --- a/config/config.go +++ b/config/config.go @@ -753,7 +753,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adoppler.endpoint", "http://{{.AccountID}}.trustedmarketplace.io/ads/processHeaderBid/{{.AdUnit}}") v.SetDefault("adapters.adot.endpoint", "https://dsp.adotmob.com/headerbidding/bidrequest") v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") - v.SetDefault("adapters.adprime.endpoint", "http://delta.adprime.com/?c=o&m=ortb") + v.SetDefault("adapters.adprime.endpoint", "http://delta.adprime.com/pserver") v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb") v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") diff --git a/openrtb_ext/imp_adprime.go b/openrtb_ext/imp_adprime.go index a089b818b56..608f5670056 100644 --- a/openrtb_ext/imp_adprime.go +++ b/openrtb_ext/imp_adprime.go @@ -2,5 +2,7 @@ package openrtb_ext // ExtImpAdprime defines adprime specifiec param type ExtImpAdprime struct { - TagID string `json:"TagID"` + TagID string `json:"TagID"` + Keywords []string `json:"keywords"` + Audiences []string `json:"audiences"` } diff --git a/static/bidder-info/adprime.yaml b/static/bidder-info/adprime.yaml index d702521ec75..4e5ff75fb71 100644 --- a/static/bidder-info/adprime.yaml +++ b/static/bidder-info/adprime.yaml @@ -5,7 +5,9 @@ capabilities: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native diff --git a/static/bidder-params/adprime.json b/static/bidder-params/adprime.json index d527056597d..23f7cb947ac 100644 --- a/static/bidder-params/adprime.json +++ b/static/bidder-params/adprime.json @@ -2,13 +2,28 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Adprime Adapter Params", "description": "A schema which validates params accepted by the Adprime adapter", - "type": "object", "properties": { - "TagID": { - "type": "string", - "description": "An ID which identifies the adprime ad tag" - } + "TagID": { + "type": "string", + "description": "An ID which identifies the adprime ad tag" + }, + "keywords": { + "type": "array", + "description": "page context keywords", + "items": { + "type": "string" + } + }, + "audiences": { + "type": "array", + "description": "publisher audiences", + "items": { + "type": "string" + } + } }, - "required" : [ "TagID" ] - } \ No newline at end of file + "required": [ + "TagID" + ] +} \ No newline at end of file From 54a4dab4b68ea9de359c05c7cfc9e0e7519fdcc2 Mon Sep 17 00:00:00 2001 From: bretg Date: Thu, 9 Sep 2021 11:24:15 -0400 Subject: [PATCH 084/140] algorix: updating contact info (#1993) --- static/bidder-info/algorix.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/bidder-info/algorix.yaml b/static/bidder-info/algorix.yaml index b8301d6cb79..75ab4c09123 100644 --- a/static/bidder-info/algorix.yaml +++ b/static/bidder-info/algorix.yaml @@ -1,8 +1,8 @@ maintainer: - email: "xunyunbo@algorix.co" + email: "prebid@algorix.co" capabilities: app: mediaTypes: - banner - video - - native \ No newline at end of file + - native From 16a10c8549cd2dd90e560dde14258df3356121c9 Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Thu, 9 Sep 2021 17:58:46 +0200 Subject: [PATCH 085/140] Split multi-imp request in multiple Improve Digital requests. (#1989) --- adapters/improvedigital/improvedigital.go | 25 +- .../exemplary/app-multi.json | 264 +++++++++++------- .../exemplary/site-multi.json | 254 ++++++++++------- 3 files changed, 331 insertions(+), 212 deletions(-) diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go index 9b2d571d903..d6318223174 100644 --- a/adapters/improvedigital/improvedigital.go +++ b/adapters/improvedigital/improvedigital.go @@ -18,23 +18,38 @@ type ImprovedigitalAdapter struct { // MakeRequests makes the HTTP requests which should be made to fetch bids. func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var errors = make([]error, 0) + numRequests := len(request.Imp) + errors := make([]error, 0) + adapterRequests := make([]*adapters.RequestData, 0, numRequests) + + // Split multi-imp request into multiple ad server requests. SRA is currently not recommended. + for i := 0; i < numRequests; i++ { + if adapterReq, err := a.makeRequest(*request, request.Imp[i]); err == nil { + adapterRequests = append(adapterRequests, adapterReq) + } else { + errors = append(errors, err) + } + } + + return adapterRequests, errors +} +func (a *ImprovedigitalAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) { + request.Imp = []openrtb2.Imp{imp} reqJSON, err := json.Marshal(request) if err != nil { - errors = append(errors, err) - return nil, errors + return nil, err } headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") - return []*adapters.RequestData{{ + return &adapters.RequestData{ Method: "POST", Uri: a.endpoint, Body: reqJSON, Headers: headers, - }}, errors + }, nil } // MakeBids unpacks the server's response into Bids. diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json b/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json index 1aee6bdf2c7..6963e82f9c4 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/app-multi.json @@ -2,21 +2,24 @@ "mockBidRequest": { "id": "test-request-id", "app": { - "id": "appID", + "id": "appID", "publisher": { "id": "uniq_pub_id" } }, - "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" - }, - "imp": [{ + "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" + }, + "imp": [ + { "id": "test-imp-id-banner", "banner": { - "format": [{ - "w": 300, - "h": 250 - }] + "format": [ + { + "w": 300, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -40,113 +43,160 @@ } ] }, - - "httpCalls": [{ - "expectedRequest": { - "uri": "http://localhost/pbs", - "body": { - "id": "test-request-id", - "app": { - "id": "appID", - "publisher": { - "id": "uniq_pub_id" - } - }, - "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" - }, - "imp": [{ - "id": "test-imp-id-banner", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "bidder": { - "placementId": 13245 - } + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "app": { + "id": "appID", + "publisher": { + "id": "uniq_pub_id" } }, - { - "id": "test-imp-id-video", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1920, - "h": 1080 - }, - "ext": { - "bidder": { - "placementId": 13244 + "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" + }, + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } } } - } - ] + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid1", + "impid": "test-imp-id-banner", + "price": 0.5, + "adid": "12345678", + "adm": "some-test-ad-html", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } } }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [{ - "seat": "improvedigital", - "bid": [{ - "id": "randomid1", - "impid": "test-imp-id-banner", - "price": 0.500000, - "adid": "12345678", - "adm": "some-test-ad-html", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300 - }, + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "app": { + "id": "appID", + "publisher": { + "id": "uniq_pub_id" + } + }, + "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" + }, + "imp": [ { - "id": "randomid2", - "impid": "test-imp-id-video", - "price": 0.500000, - "adm": "some-test-ad-vast", - "crid": "1234567", - "w": 1920, - "h": 1080 + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } } ] - }], - "cur": "USD" - } - } - }], - - "expectedBidResponses": [{ - "currency": "USD", - "bids": [{ - "bid": { - "id": "randomid1", - "impid": "test-imp-id-banner", - "price": 0.5, - "adm": "some-test-ad-html", - "adid": "12345678", - "cid": "987", - "crid": "12345678", - "w": 300, - "h": 250 - }, - "type": "banner" + } }, - { - "bid": { - "id": "randomid2", - "impid": "test-imp-id-video", - "price": 0.5, - "adm": "some-test-ad-vast", - "crid": "1234567", - "w": 1920, - "h": 1080 - }, - "type": "video" + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid2", + "impid": "test-imp-id-video", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080 + } + ] + } + ], + "cur": "USD" + } } - ] - }] + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid1", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-html", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid2", + "impid": "test-imp-id-video", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080 + }, + "type": "video" + } + ] + } + ] } diff --git a/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json b/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json index 5fb4f061f50..0945b4c0f69 100644 --- a/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json +++ b/adapters/improvedigital/improvedigitaltest/exemplary/site-multi.json @@ -12,13 +12,16 @@ "amp": 0 } }, - "imp": [{ + "imp": [ + { "id": "test-imp-id-banner", "banner": { - "format": [{ - "w": 300, - "h": 250 - }] + "format": [ + { + "w": 300, + "h": 250 + } + ] }, "ext": { "bidder": { @@ -43,114 +46,165 @@ ] }, - "httpCalls": [{ - "expectedRequest": { - "uri": "http://localhost/pbs", - "body": { - "id": "test-request-id", - "site": { - "page": "https://good.site/url", - "domain": "good.site", - "publisher": { - "id": "uniq_pub_id" - }, - "keywords": "omgword", - "ext": { - "amp": 0 - } - }, - "imp": [{ - "id": "test-imp-id-banner", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "domain": "good.site", + "publisher": { + "id": "uniq_pub_id" }, + "keywords": "omgword", "ext": { - "bidder": { - "placementId": 13245 - } + "amp": 0 } }, - { - "id": "test-imp-id-video", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1920, - "h": 1080 - }, - "ext": { - "bidder": { - "placementId": 13244 + "imp": [ + { + "id": "test-imp-id-banner", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placementId": 13245 + } } } - } - ] + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid1", + "impid": "test-imp-id-banner", + "price": 0.5, + "adid": "12345678", + "adm": "some-test-ad-html", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + } + ] + } + ], + "cur": "USD" + } } }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [{ - "seat": "improvedigital", - "bid": [{ - "id": "randomid1", - "impid": "test-imp-id-banner", - "price": 0.500000, - "adid": "12345678", - "adm": "some-test-ad-html", - "cid": "987", - "crid": "12345678", - "h": 250, - "w": 300 + { + "expectedRequest": { + "uri": "http://localhost/pbs", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url", + "domain": "good.site", + "publisher": { + "id": "uniq_pub_id" }, + "keywords": "omgword", + "ext": { + "amp": 0 + } + }, + "imp": [ { - "id": "randomid2", - "impid": "test-imp-id-video", - "price": 0.500000, - "adm": "some-test-ad-vast", - "crid": "1234567", - "w": 1920, - "h": 1080 + "id": "test-imp-id-video", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1920, + "h": 1080 + }, + "ext": { + "bidder": { + "placementId": 13244 + } + } } ] - }], - "cur": "USD" + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "improvedigital", + "bid": [ + { + "id": "randomid2", + "impid": "test-imp-id-video", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080 + } + ] + } + ], + "cur": "USD" + } } } - }], + ], - "expectedBidResponses": [{ - "currency": "USD", - "bids": [{ - "bid": { - "id": "randomid1", - "impid": "test-imp-id-banner", - "price": 0.5, - "adm": "some-test-ad-html", - "adid": "12345678", - "cid": "987", - "crid": "12345678", - "w": 300, - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "id": "randomid2", - "impid": "test-imp-id-video", - "price": 0.5, - "adm": "some-test-ad-vast", - "crid": "1234567", - "w": 1920, - "h": 1080 - }, - "type": "video" - } - ] - }] + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid1", + "impid": "test-imp-id-banner", + "price": 0.5, + "adm": "some-test-ad-html", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "randomid2", + "impid": "test-imp-id-video", + "price": 0.5, + "adm": "some-test-ad-vast", + "crid": "1234567", + "w": 1920, + "h": 1080 + }, + "type": "video" + } + ] + } + ] } From 664ac8a0b58352008653d9132052e2d77f8c37d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Fri, 10 Sep 2021 21:04:19 +0200 Subject: [PATCH 086/140] Send x-prebid header in requests (#1981) --- Dockerfile | 2 +- endpoints/version.go | 11 +++- endpoints/version_test.go | 18 +++--- exchange/bidder.go | 21 +++---- exchange/bidder_test.go | 13 ++++- exchange/utils.go | 37 +++++++++++++ exchange/utils_test.go | 114 ++++++++++++++++++++++++++++++++++++++ main.go | 14 +---- openrtb_ext/request.go | 7 +++ router/admin.go | 5 +- version/version.go | 14 +++++ 11 files changed, 219 insertions(+), 37 deletions(-) create mode 100644 version/version.go diff --git a/Dockerfile b/Dockerfile index defb64c8586..6ce9a7dfa38 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN go mod vendor RUN go mod tidy ARG TEST="true" RUN if [ "$TEST" != "false" ]; then ./validate.sh ; fi -RUN go build -mod=vendor . +RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/version.Rev=`git rev-parse HEAD`" . FROM ubuntu:18.04 AS release LABEL maintainer="hans.hjort@xandr.com" diff --git a/endpoints/version.go b/endpoints/version.go index d3cda6b6e3c..514791e20b8 100644 --- a/endpoints/version.go +++ b/endpoints/version.go @@ -8,18 +8,23 @@ import ( ) type versionModel struct { + Version string `json:"version"` Revision string `json:"revision"` } -// NewVersionEndpoint returns the latest commit sha1 from which the binary was built -func NewVersionEndpoint(version string) http.HandlerFunc { +// NewVersionEndpoint returns the latest git tag as the version and commit hash as the revision from which the binary was built +func NewVersionEndpoint(version string, revision string) http.HandlerFunc { if version == "" { version = "not-set" } + if revision == "" { + revision = "not-set" + } return func(w http.ResponseWriter, _ *http.Request) { jsonOutput, err := json.Marshal(versionModel{ - Revision: version, + Version: version, + Revision: revision, }) if err != nil { glog.Errorf("/version Critical error when trying to marshal versionModel: %v", err) diff --git a/endpoints/version_test.go b/endpoints/version_test.go index 1d21a89f841..ecddf532e8f 100644 --- a/endpoints/version_test.go +++ b/endpoints/version_test.go @@ -11,17 +11,18 @@ import ( func TestVersion(t *testing.T) { // Setup: var testCases = []struct { - input string + version string + revision string expected string }{ - {"", `{"revision": "not-set"}`}, - {"abc", `{"revision": "abc"}`}, - {"d6cd1e2bd19e03a81132a23b2025920577f84e37", `{"revision": "d6cd1e2bd19e03a81132a23b2025920577f84e37"}`}, + {"", "", `{"version":"not-set","revision":"not-set"}`}, + {"abc", "def", `{"version":"abc","revision" :"def"}`}, + {"1.2.3", "d6cd1e2bd19e03a81132a23b2025920577f84e37", `{"version":"1.2.3","revision":"d6cd1e2bd19e03a81132a23b2025920577f84e37"}`}, } for _, tc := range testCases { - handler := NewVersionEndpoint(tc.input) + handler := NewVersionEndpoint(tc.version, tc.revision) w := httptest.NewRecorder() // Execute: @@ -29,7 +30,11 @@ func TestVersion(t *testing.T) { // Verify: var result, expected versionModel - err := json.NewDecoder(w.Body).Decode(&result) + responseBodyBytes, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Errorf("Error reading response body bytes: %s", err) + } + err = json.Unmarshal(responseBodyBytes, &result) if err != nil { t.Errorf("Bad response body. Expected: %s, got an error %s", tc.expected, err) } @@ -40,7 +45,6 @@ func TestVersion(t *testing.T) { } if !reflect.DeepEqual(expected, result) { - responseBodyBytes, _ := ioutil.ReadAll(w.Body) responseBodyString := string(responseBodyBytes) t.Errorf("Bad response body. Expected: %s, got %s", tc.expected, responseBodyString) } diff --git a/exchange/bidder.go b/exchange/bidder.go index 895cff936bd..28be854c264 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -15,6 +15,7 @@ import ( "github.com/golang/glog" "github.com/prebid/prebid-server/config/util" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/version" nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" @@ -138,17 +139,17 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B } return nil, errs } + xPrebidHeader := buildXPrebidHeader(request, version.Ver) - 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) - } + for i := 0; i < len(reqData); i++ { + if reqData[i].Headers != nil { + reqData[i].Headers = reqData[i].Headers.Clone() + } else { + reqData[i].Headers = http.Header{} + } + reqData[i].Headers.Add("X-Prebid", xPrebidHeader) + if reqInfo.GlobalPrivacyControlHeader == "1" { + reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader) } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index a31a195c512..1591d353bb0 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -26,6 +26,7 @@ import ( "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -145,6 +146,12 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { server := httptest.NewServer(mockHandler(200, "getBody", "responseJson")) defer server.Close() + oldVer := version.Ver + version.Ver = "test-version" + defer func() { + version.Ver = oldVer + }() + requestHeaders := http.Header{} requestHeaders.Add("Content-Type", "application/json") requestHeaders.Add("Authorization", "anySecret") @@ -173,7 +180,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { { Uri: server.URL, RequestBody: "requestJson", - RequestHeaders: map[string][]string{"Content-Type": {"application/json"}}, + RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"pbs-go/test-version"}}, ResponseBody: "responseJson", Status: 200, }, @@ -214,7 +221,7 @@ func TestSetGPCHeader(t *testing.T) { { Uri: server.URL, RequestBody: "requestJson", - RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "Sec-Gpc": {"1"}}, + RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"pbs-go/unknown"}, "Sec-Gpc": {"1"}}, ResponseBody: "responseJson", Status: 200, }, @@ -252,7 +259,7 @@ func TestSetGPCHeaderNil(t *testing.T) { { Uri: server.URL, RequestBody: "requestJson", - RequestHeaders: map[string][]string{"Sec-Gpc": {"1"}}, + RequestHeaders: map[string][]string{"X-Prebid": {"pbs-go/unknown"}, "Sec-Gpc": {"1"}}, ResponseBody: "responseJson", Status: 200, }, diff --git a/exchange/utils.go b/exchange/utils.go index f33f1b9199c..1277cffd85f 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math/rand" + "strings" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/go-gdpr/vendorconsent" @@ -700,3 +701,39 @@ func getExtBidAdjustmentFactors(requestExt *openrtb_ext.ExtRequest) map[string]f } return bidAdjustmentFactors } + +func buildXPrebidHeader(bidRequest *openrtb2.BidRequest, version string) string { + req := &openrtb_ext.RequestWrapper{BidRequest: bidRequest} + + sb := &strings.Builder{} + writeNameVersionRecord(sb, "pbs-go", version) + + if reqExt, err := req.GetRequestExt(); err == nil && reqExt != nil { + if prebidExt := reqExt.GetPrebid(); prebidExt != nil { + if channel := prebidExt.Channel; channel != nil { + writeNameVersionRecord(sb, channel.Name, channel.Version) + } + } + } + if appExt, err := req.GetAppExt(); err == nil && appExt != nil { + if prebidExt := appExt.GetPrebid(); prebidExt != nil { + writeNameVersionRecord(sb, prebidExt.Source, prebidExt.Version) + } + } + return sb.String() +} + +func writeNameVersionRecord(sb *strings.Builder, name, version string) { + if name == "" { + return + } + if version == "" { + version = "unknown" + } + if sb.Len() != 0 { + sb.WriteString(",") + } + sb.WriteString(name) + sb.WriteString("/") + sb.WriteString(version) +} diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 81b0b9bd3c0..6ce4b28ac33 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -2228,3 +2228,117 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { assert.Equal(t, &requestExpected, test.request, test.description+":request") } } + +func TestBuildXPrebidHeader(t *testing.T) { + testCases := []struct { + description string + version string + requestExt *openrtb_ext.ExtRequest + requestAppExt *openrtb_ext.ExtApp + result string + }{ + { + description: "No versions", + version: "", + result: "pbs-go/unknown", + }, + { + description: "pbs", + version: "test-version", + result: "pbs-go/test-version", + }, + { + description: "prebid.js", + version: "test-version", + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Channel: &openrtb_ext.ExtRequestPrebidChannel{ + Name: "pbjs", + Version: "test-pbjs-version", + }, + }, + }, + result: "pbs-go/test-version,pbjs/test-pbjs-version", + }, + { + description: "unknown prebid.js", + version: "test-version", + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Channel: &openrtb_ext.ExtRequestPrebidChannel{ + Name: "pbjs", + }, + }, + }, + result: "pbs-go/test-version,pbjs/unknown", + }, + { + description: "channel without a name", + version: "test-version", + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Channel: &openrtb_ext.ExtRequestPrebidChannel{ + Version: "test-version", + }, + }, + }, + result: "pbs-go/test-version", + }, + { + description: "prebid-mobile", + version: "test-version", + requestAppExt: &openrtb_ext.ExtApp{ + Prebid: openrtb_ext.ExtAppPrebid{ + Source: "prebid-mobile", + Version: "test-prebid-mobile-version", + }, + }, + result: "pbs-go/test-version,prebid-mobile/test-prebid-mobile-version", + }, + { + description: "app ext without a source", + version: "test-version", + requestAppExt: &openrtb_ext.ExtApp{ + Prebid: openrtb_ext.ExtAppPrebid{ + Version: "test-version", + }, + }, + result: "pbs-go/test-version", + }, + { + description: "Version found in both req.Ext and req.App.Ext", + version: "test-version", + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Channel: &openrtb_ext.ExtRequestPrebidChannel{ + Name: "pbjs", + Version: "test-pbjs-version", + }, + }, + }, + requestAppExt: &openrtb_ext.ExtApp{ + Prebid: openrtb_ext.ExtAppPrebid{ + Source: "prebid-mobile", + Version: "test-prebid-mobile-version", + }, + }, + result: "pbs-go/test-version,pbjs/test-pbjs-version,prebid-mobile/test-prebid-mobile-version", + }, + } + + for _, test := range testCases { + req := &openrtb2.BidRequest{} + if test.requestExt != nil { + reqExt, err := json.Marshal(test.requestExt) + assert.NoError(t, err, test.description+":err marshalling reqExt") + req.Ext = reqExt + } + if test.requestAppExt != nil { + reqAppExt, err := json.Marshal(test.requestAppExt) + assert.NoError(t, err, test.description+":err marshalling reqAppExt") + req.App = &openrtb2.App{Ext: reqAppExt} + } + result := buildXPrebidHeader(req, test.version) + assert.Equal(t, test.result, result, test.description+":result") + } +} diff --git a/main.go b/main.go index 5c5a2a462e0..6087c3d69dd 100644 --- a/main.go +++ b/main.go @@ -17,14 +17,6 @@ import ( "github.com/spf13/viper" ) -// Rev holds binary revision string -// Set manually at build time using: -// go build -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -// Populated automatically at build / releases -// `gox -os="linux" -arch="386" -output="{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...;` -// See issue #559 -var Rev string - func init() { rand.Seed(time.Now().UnixNano()) } @@ -37,7 +29,7 @@ func main() { glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err) } - err = serve(Rev, cfg) + err = serve(cfg) if err != nil { glog.Exitf("prebid-server failed: %v", err) } @@ -51,7 +43,7 @@ func loadConfig() (*config.Configuration, error) { return config.New(v) } -func serve(revision string, cfg *config.Configuration) error { +func serve(cfg *config.Configuration) error { fetchingInterval := time.Duration(cfg.CurrencyConverter.FetchIntervalSeconds) * time.Second staleRatesThreshold := time.Duration(cfg.CurrencyConverter.StaleRatesSeconds) * time.Second currencyConverter := currency.NewRateConverter(&http.Client{}, cfg.CurrencyConverter.FetchURL, staleRatesThreshold) @@ -67,7 +59,7 @@ func serve(revision string, cfg *config.Configuration) error { pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL()) corsRouter := router.SupportCORS(r) - server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(revision, currencyConverter, fetchingInterval), r.MetricsEngine) + server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(currencyConverter, fetchingInterval), r.MetricsEngine) r.Shutdown() return nil diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 606874f196a..bf875123ea1 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -29,6 +29,7 @@ type ExtRequestPrebid struct { Aliases map[string]string `json:"aliases,omitempty"` BidAdjustmentFactors map[string]float64 `json:"bidadjustmentfactors,omitempty"` Cache *ExtRequestPrebidCache `json:"cache,omitempty"` + Channel *ExtRequestPrebidChannel `json:"channel,omitempty"` Data *ExtRequestPrebidData `json:"data,omitempty"` Debug bool `json:"debug,omitempty"` Events json.RawMessage `json:"events,omitempty"` @@ -80,6 +81,12 @@ type SourceExt struct { SChain ExtRequestPrebidSChainSChain `json:"schain"` } +// ExtRequestPrebidChannel defines the contract for bidrequest.ext.prebid.channel +type ExtRequestPrebidChannel struct { + Name string `json:"name"` + Version string `json:"version"` +} + // ExtRequestPrebidCache defines the contract for bidrequest.ext.prebid.cache type ExtRequestPrebidCache struct { Bids *ExtRequestPrebidCacheBids `json:"bids"` diff --git a/router/admin.go b/router/admin.go index b66bf55a5d6..29cdbbe5e23 100644 --- a/router/admin.go +++ b/router/admin.go @@ -7,9 +7,10 @@ import ( "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/endpoints" + "github.com/prebid/prebid-server/version" ) -func Admin(revision string, rateConverter *currency.RateConverter, rateConverterFetchingInterval time.Duration) *http.ServeMux { +func Admin(rateConverter *currency.RateConverter, rateConverterFetchingInterval time.Duration) *http.ServeMux { // Add endpoints to the admin server // Making sure to add pprof routes mux := http.NewServeMux() @@ -21,6 +22,6 @@ func Admin(revision string, rateConverter *currency.RateConverter, rateConverter mux.HandleFunc("/debug/pprof/trace", pprof.Trace) // Register prebid-server defined admin handlers mux.HandleFunc("/currency/rates", endpoints.NewCurrencyRatesEndpoint(rateConverter, rateConverterFetchingInterval)) - mux.HandleFunc("/version", endpoints.NewVersionEndpoint(revision)) + mux.HandleFunc("/version", endpoints.NewVersionEndpoint(version.Ver, version.Rev)) return mux } diff --git a/version/version.go b/version/version.go new file mode 100644 index 00000000000..e93102763fa --- /dev/null +++ b/version/version.go @@ -0,0 +1,14 @@ +package version + +// Ver holds the version derived from the latest git tag +// Populated using: +// go build -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//`" +// Populated automatically at build / releases in the Docker image +var Ver string + +// Rev holds binary revision string +// Populated using: +// go build -ldflags "-X github.com/prebid/prebid-server/version.Rev=`git rev-parse --short HEAD`" +// Populated automatically at build / releases in the Docker image +// See issue #559 +var Rev string From c3fcbca52378f7f9b44a0139f8d5949111339a13 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 13 Sep 2021 13:28:17 -0400 Subject: [PATCH 087/140] Persist bid.ext.prebid.meta From Bidding Server Response (#1983) --- exchange/exchange.go | 18 ++++++-- exchange/exchange_test.go | 68 +++++++++++++++++++++++++++++- openrtb_ext/bid.go | 27 ++++++------ util/maputil/maputil.go | 22 +++++++++- util/maputil/maputil_test.go | 81 ++++++++++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 19 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index c612a35bd3c..f6ec713fa28 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -24,6 +24,7 @@ import ( "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/maputil" "github.com/buger/jsonparser" "github.com/gofrs/uuid" @@ -966,9 +967,6 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool } func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string) (json.RawMessage, error) { - // update existing bid.ext with prebid section - // if bid.ext.prebid already exists, it will be overwritten. - // if echoVideoAttrs set to true stored video attributes will be added to bid.ext.storedrequestattributes var extMap map[string]interface{} if len(ext) != 0 { @@ -979,10 +977,22 @@ func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impEx extMap = make(map[string]interface{}) } + // ext.prebid + if prebid.Meta == nil && maputil.HasElement(extMap, "prebid", "meta") { + metaContainer := struct { + Prebid struct { + Meta openrtb_ext.ExtBidPrebidMeta `json:"meta"` + } `json:"prebid"` + }{} + if err := json.Unmarshal(ext, &metaContainer); err != nil { + return nil, fmt.Errorf("error validaing response from server, %s", err) + } + prebid.Meta = &metaContainer.Prebid.Meta + } extMap[openrtb_ext.PrebidExtKey] = prebid + // ext.storedrequestattributes if impExtInfo, ok := impExtInfoMap[impId]; ok && impExtInfo.EchoVideoAttrs { - videoData, _, _, err := jsonparser.Get(impExtInfo.StoredImp, "video") if err != nil && err != jsonparser.KeyPathNotFoundError { return nil, err diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 706d118de4f..ed378e7b021 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -3491,11 +3491,19 @@ func TestMakeBidExtJSON(t *testing.T) { testCases := []aTest{ { - description: "Valid extension, non empty extBidPrebid and valid imp ext info", + description: "Valid extension, non empty extBidPrebid, valid imp ext info, meta from adapter", ext: json.RawMessage(`{"video":{"h":100}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}}, + impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}}, + expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}}`, + expectedErrMessage: "", + }, + { + description: "Valid extension, non empty extBidPrebid, valid imp ext info, meta from response", + ext: json.RawMessage(`{"video":{"h":100},"prebid":{"meta": {"brandName": "foo"}}}`), extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")}, impExtInfo: map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}}, - expectedBidExt: `{"prebid":{"type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}}`, + expectedBidExt: `{"prebid":{"meta": {"brandName": "foo"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}}`, expectedErrMessage: "", }, { @@ -3554,6 +3562,54 @@ func TestMakeBidExtJSON(t *testing.T) { expectedBidExt: `{"prebid":{"type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`, expectedErrMessage: "", }, + { + description: "Meta - Defined By Bid - Nil Extension", + ext: nil, + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}}, + impExtInfo: map[string]ImpExtInfo{}, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}}`, + expectedErrMessage: "", + }, + { + description: "Meta - Defined By Bid - Empty Extension", + ext: json.RawMessage(`{}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}}, + impExtInfo: nil, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}}`, + expectedErrMessage: "", + }, + { + description: "Meta - Defined By Bid - Existing Extension Overwritten", + ext: json.RawMessage(`{"prebid":{"meta":{"brandName":"notfoo", "brandId": 42}}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}}, + impExtInfo: nil, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}}`, + expectedErrMessage: "", + }, + { + description: "Meta - Not Defined By Bid - Persists From Bid Ext", + ext: json.RawMessage(`{"prebid":{"meta":{"brandName":"foo"}}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")}, + impExtInfo: nil, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}}`, + expectedErrMessage: "", + }, + { + description: "Meta - Not Defined By Bid - Persists From Bid Ext - Invalid Fields Ignored", + ext: json.RawMessage(`{"prebid":{"meta":{"brandName":"foo","unknown":"value"}}}`), + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")}, + impExtInfo: nil, + expectedBidExt: `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}}`, + expectedErrMessage: "", + }, + { + description: "Meta - Not Defined", + ext: nil, + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")}, + impExtInfo: nil, + expectedBidExt: `{"prebid":{"type":"banner"}}`, + expectedErrMessage: "", + }, //Error cases { description: "Invalid extension, valid extBidPrebid and valid imp ext info", @@ -3571,6 +3627,14 @@ func TestMakeBidExtJSON(t *testing.T) { expectedBidExt: ``, expectedErrMessage: "invalid character", }, + { + description: "Meta - Invalid", + ext: json.RawMessage(`{"prebid":{"meta":{"brandId":"foo"}}}`), // brandId should be an int, but is a string in this test case + extBidPrebid: openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")}, + impExtInfo: nil, + expectedErrMessage: "error validaing response from server, json: cannot unmarshal string into Go struct field ExtBidPrebidMeta.prebid.meta.brandId of type int", + }, + // add invalid } for _, test := range testCases { diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index ce6e25fd0e7..7c73fb0d533 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -1,6 +1,7 @@ package openrtb_ext import ( + "encoding/json" "fmt" ) @@ -38,18 +39,20 @@ type ExtBidPrebidCacheBids struct { // ExtBidPrebidMeta defines the contract for bidresponse.seatbid.bid[i].ext.prebid.meta type ExtBidPrebidMeta struct { - AdvertiserDomains []string `json:"advertiserDomains,omitempty"` // or advertiserDomain? - AdvertiserID int `json:"advertiserId,omitempty"` - AdvertiserName string `json:"advertiserName,omitempty"` - AgencyID int `json:"agencyId,omitempty"` - AgencyName string `json:"agencyName,omitempty"` - BrandID int `json:"brandId,omitempty"` - BrandName string `json:"brandName,omitempty"` - MediaType string `json:"mediaType,omitempty"` - NetworkID int `json:"networkId,omitempty"` - NetworkName string `json:"networkName,omitempty"` - PrimaryCategoryID string `json:"primaryCatId,omitempty"` - SecondaryCategoryIDs []string `json:"secondaryCatIds,omitempty"` + AdvertiserDomains []string `json:"advertiserDomains,omitempty"` + AdvertiserID int `json:"advertiserId,omitempty"` + AdvertiserName string `json:"advertiserName,omitempty"` + AgencyID int `json:"agencyId,omitempty"` + AgencyName string `json:"agencyName,omitempty"` + BrandID int `json:"brandId,omitempty"` + BrandName string `json:"brandName,omitempty"` + DemandSource string `json:"demandSource,omitempty"` + DChain json.RawMessage `json:"dchain,omitempty"` + MediaType string `json:"mediaType,omitempty"` + NetworkID int `json:"networkId,omitempty"` + NetworkName string `json:"networkName,omitempty"` + PrimaryCategoryID string `json:"primaryCatId,omitempty"` + SecondaryCategoryIDs []string `json:"secondaryCatIds,omitempty"` } // ExtBidPrebidVideo defines the contract for bidresponse.seatbid.bid[i].ext.prebid.video diff --git a/util/maputil/maputil.go b/util/maputil/maputil.go index 595e93211e2..49df0772ba6 100644 --- a/util/maputil/maputil.go +++ b/util/maputil/maputil.go @@ -20,7 +20,7 @@ func ReadEmbeddedSlice(m map[string]interface{}, k string) ([]interface{}, bool) return nil, false } -// ReadEmbeddedString reads element k from the map m as a string +// ReadEmbeddedString reads element k from the map m as a string. func ReadEmbeddedString(m map[string]interface{}, k string) (string, bool) { if v, ok := m[k]; ok { vCasted, ok := v.(string) @@ -28,3 +28,23 @@ func ReadEmbeddedString(m map[string]interface{}, k string) (string, bool) { } return "", false } + +// HasElement returns true if nested element k exists. +func HasElement(m map[string]interface{}, k ...string) bool { + exists := false + kLastIndex := len(k) - 1 + + for i, k := range k { + isLastKey := i == kLastIndex + + if isLastKey { + _, exists = m[k] + } else { + if m, exists = ReadEmbeddedMap(m, k); !exists { + break + } + } + } + + return exists +} diff --git a/util/maputil/maputil_test.go b/util/maputil/maputil_test.go index abce102f5f5..114c23327bd 100644 --- a/util/maputil/maputil_test.go +++ b/util/maputil/maputil_test.go @@ -164,3 +164,84 @@ func TestReadEmbeddedString(t *testing.T) { assert.Equal(t, test.expectedOK, resultOK, test.description+":ok") } } + +func TestHasElement(t *testing.T) { + testCases := []struct { + description string + value map[string]interface{} + keys []string + expected bool + }{ + { + description: "Level 1 - Exists", + value: map[string]interface{}{"foo": "exists"}, + keys: []string{"foo"}, + expected: true, + }, + { + description: "Level 1 - Does Not Exist", + value: map[string]interface{}{"foo": "exists"}, + keys: []string{"doesnotexist"}, + expected: false, + }, + { + description: "Level 2 - Exists", + value: map[string]interface{}{"foo": map[string]interface{}{"bar": "exists"}}, + keys: []string{"foo", "bar"}, + expected: true, + }, + { + description: "Level 2 - Top Level Does Not Exist", + value: map[string]interface{}{"foo": map[string]interface{}{"bar": "exists"}}, + keys: []string{"doesnotexist", "bar"}, + expected: false, + }, + { + description: "Level 2 - Lower Level Does Not Exist", + value: map[string]interface{}{"foo": map[string]interface{}{"bar": "exists"}}, + keys: []string{"foo", "doesnotexist"}, + expected: false, + }, + { + description: "Level 2 - Does Not Exist At All", + value: map[string]interface{}{"foo": map[string]interface{}{"bar": "exists"}}, + keys: []string{"doesnotexist", "doesnotexist"}, + expected: false, + }, + { + description: "Keys Nil", + value: map[string]interface{}{"foo": "exists"}, + keys: nil, + expected: false, + }, + { + description: "Keys Empty", + value: map[string]interface{}{"foo": "exists"}, + keys: []string{}, + expected: false, + }, + { + description: "Map Nil", + value: nil, + keys: []string{"foo"}, + expected: false, + }, + { + description: "Map Empty", + value: map[string]interface{}{}, + keys: []string{"foo"}, + expected: false, + }, + { + description: "Nil", + value: nil, + keys: nil, + expected: false, + }, + } + + for _, test := range testCases { + result := HasElement(test.value, test.keys...) + assert.Equal(t, test.expected, result, test.description) + } +} From 24abd9c08981a49e9621ec34c05289e187f19a81 Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Tue, 14 Sep 2021 11:13:13 -0700 Subject: [PATCH 088/140] Generate Unique Bidrequest IDs for Amp Auctions (#1938) --- config/config.go | 3 + endpoints/openrtb2/amp_auction.go | 12 ++ endpoints/openrtb2/amp_auction_test.go | 112 ++++++++--- endpoints/openrtb2/auction.go | 81 ++++++-- endpoints/openrtb2/auction_benchmark_test.go | 1 + endpoints/openrtb2/auction_test.go | 201 +++++++++++++++++++ endpoints/openrtb2/video_auction.go | 3 + endpoints/openrtb2/video_auction_test.go | 4 + router/router.go | 19 +- util/uuidutil/uuidutil.go | 19 ++ 10 files changed, 406 insertions(+), 49 deletions(-) create mode 100644 util/uuidutil/uuidutil.go diff --git a/config/config.go b/config/config.go index 0286744df4a..f6f6f187602 100644 --- a/config/config.go +++ b/config/config.go @@ -83,6 +83,8 @@ type Configuration struct { AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"` //When true, new bid id will be generated in seatbid[].bid[].ext.prebid.bidid and used in event urls instead GenerateBidID bool `mapstructure:"generate_bid_id"` + // GenerateRequestID overrides the bidrequest.id in an AMP Request or an App Stored Request with a generated UUID if set to true. The default is false. + GenerateRequestID bool `mapstructure:"generate_request_id"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -944,6 +946,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("certificates_file", "") v.SetDefault("auto_gen_source_tid", true) v.SetDefault("generate_bid_id", false) + v.SetDefault("generate_request_id", false) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 7a0afb0d0d6..159e8a15922 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -31,6 +31,7 @@ import ( "github.com/golang/glog" "github.com/julienschmidt/httprouter" "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/util/uuidutil" ) const defaultAmpRequestTimeoutMillis = 900 @@ -45,6 +46,7 @@ type AmpResponse struct { // NewAmpEndpoint modifies the OpenRTB endpoint to handle AMP requests. This will basically modify the parsing // of the request, and the return value, using the OpenRTB machinery to handle everything in between. func NewAmpEndpoint( + uuidGenerator uuidutil.UUIDGenerator, ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, @@ -69,6 +71,7 @@ func NewAmpEndpoint( } return httprouter.Handle((&endpointDeps{ + uuidGenerator, ex, validator, requestsById, @@ -345,6 +348,15 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return } + if deps.cfg.GenerateRequestID { + newBidRequestId, err := deps.uuidGenerator.Generate() + if err != nil { + errs = []error{err} + return + } + req.ID = newBidRequestId + } + if ampParams.Debug { req.Test = 1 } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 667c4aefd39..ae3c036aa5e 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -11,6 +11,8 @@ import ( "strconv" "testing" + "github.com/julienschmidt/httprouter" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" analyticsConf "github.com/prebid/prebid-server/analytics/config" "github.com/prebid/prebid-server/config" @@ -19,7 +21,6 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -38,6 +39,7 @@ func TestGoodAmpRequests(t *testing.T) { } endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, &mockAmpExchange{}, newParamsValidator(t), &mockAmpStoredReqFetcher{goodRequests}, @@ -91,6 +93,7 @@ func TestAMPPageInfo(t *testing.T) { exchange := &mockAmpExchange{} endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, exchange, newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, @@ -188,6 +191,7 @@ func TestGDPRConsent(t *testing.T) { // Build Exchange Endpoint mockExchange := &mockAmpExchange{} endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, mockExchange, newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, @@ -340,6 +344,7 @@ func TestCCPAConsent(t *testing.T) { // Build Exchange Endpoint mockExchange := &mockAmpExchange{} endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, mockExchange, newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, @@ -450,6 +455,7 @@ func TestConsentWarnings(t *testing.T) { mockExchange = &mockAmpExchange{} } endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, mockExchange, newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, @@ -542,6 +548,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { // Build Exchange Endpoint mockExchange := &mockAmpExchange{} endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, mockExchange, newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, @@ -593,6 +600,7 @@ func TestAMPSiteExt(t *testing.T) { } exchange := &mockAmpExchange{} endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, exchange, newParamsValidator(t), &mockAmpStoredReqFetcher{stored}, @@ -629,6 +637,7 @@ func TestAmpBadRequests(t *testing.T) { } endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, &mockAmpExchange{}, newParamsValidator(t), &mockAmpStoredReqFetcher{badRequests}, @@ -659,6 +668,7 @@ func TestAmpDebug(t *testing.T) { } endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, &mockAmpExchange{}, newParamsValidator(t), &mockAmpStoredReqFetcher{requests}, @@ -731,6 +741,7 @@ func TestQueryParamOverrides(t *testing.T) { } endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, &mockAmpExchange{}, newParamsValidator(t), &mockAmpStoredReqFetcher{requests}, @@ -883,6 +894,7 @@ func (s formatOverrideSpec) execute(t *testing.T) { } endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{}, &mockAmpExchange{}, newParamsValidator(t), &mockAmpStoredReqFetcher{requests}, @@ -1249,31 +1261,8 @@ func TestBuildAmpObject(t *testing.T) { recorder := httptest.NewRecorder() for _, test := range testCases { - // Set up test, declare a new mock logger every time - actualAmpObject := new(analytics.AmpObject) - - logger := newMockLogger(actualAmpObject) - - mockAmpFetcher := &mockAmpStoredReqFetcher{ - data: map[string]json.RawMessage{ - test.inTagId: json.RawMessage(test.inStoredRequest), - }, - } - - endpoint, _ := NewAmpEndpoint( - &mockAmpExchange{}, - newParamsValidator(t), - mockAmpFetcher, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, - logger, - map[string]string{}, - []byte{}, - openrtb_ext.BuildBidderMap(), - ) - + actualAmpObject, endpoint := AmpObjectTestSetup(t, test.inTagId, test.inStoredRequest, false) // Run test endpoint(recorder, request, nil) @@ -1286,3 +1275,76 @@ func TestBuildAmpObject(t *testing.T) { assert.Equalf(t, test.expectedAmpObject.Origin, actualAmpObject.Origin, "Amp Object Origin field doesn't match expected: %s\n", test.description) } } + +func TestIdGeneration(t *testing.T) { + uuid := "foo" + + testCases := []struct { + description string + givenInStoredRequest json.RawMessage + givenGenerateRequestID bool + expectedID string + }{ + { + description: "The givenGenerateRequestID flag is set to true, so even though the stored amp request already has an id, we should still generate a new uuid", + givenInStoredRequest: json.RawMessage(`{"id":"ThisID","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`), + givenGenerateRequestID: true, + expectedID: uuid, + }, + { + description: "The givenGenerateRequestID flag is set to true and the stored amp request ID is blank, so we should generate a new uuid for the request", + givenInStoredRequest: json.RawMessage(`{"id":"","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`), + givenGenerateRequestID: true, + expectedID: uuid, + }, + { + description: "The givenGenerateRequestID flag is false, so the ID shouldn't change", + givenInStoredRequest: json.RawMessage(`{"id":"ThisID","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`), + givenGenerateRequestID: false, + expectedID: "ThisID", + }, + { + description: "The givenGenerateRequestID flag is true, and if the id field isn't included in the stored request", + givenInStoredRequest: json.RawMessage(`{"site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`), + givenGenerateRequestID: true, + expectedID: uuid, + }, + } + + request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=test", nil) + recorder := httptest.NewRecorder() + + for _, test := range testCases { + // Set up and run test + actualAmpObject, endpoint := AmpObjectTestSetup(t, "test", test.givenInStoredRequest, test.givenGenerateRequestID) + endpoint(recorder, request, nil) + + assert.Equalf(t, test.expectedID, actualAmpObject.Request.ID, "Bid Request ID is incorrect: %s\n", test.description) + } +} + +func AmpObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMessage, generateRequestID bool) (*analytics.AmpObject, httprouter.Handle) { + actualAmpObject := analytics.AmpObject{} + logger := newMockLogger(&actualAmpObject) + + mockAmpFetcher := &mockAmpStoredReqFetcher{ + data: map[string]json.RawMessage{ + inTagId: json.RawMessage(inStoredRequest), + }, + } + + endpoint, _ := NewAmpEndpoint( + fakeUUIDGenerator{id: "foo", err: nil}, + &mockAmpExchange{}, + newParamsValidator(t), + mockAmpFetcher, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize, GenerateRequestID: generateRequestID}, + &metricsConfig.DummyMetricsEngine{}, + logger, + map[string]string{}, + []byte{}, + openrtb_ext.BuildBidderMap(), + ) + return &actualAmpObject, endpoint +} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 1d879443a5d..579d6687034 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -14,7 +14,7 @@ import ( "time" "github.com/buger/jsonparser" - "github.com/evanphx/json-patch" + jsonpatch "github.com/evanphx/json-patch" "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -36,6 +36,7 @@ import ( "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/httputil" "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/uuidutil" "golang.org/x/net/publicsuffix" "golang.org/x/text/currency" ) @@ -49,6 +50,7 @@ var ( ) func NewEndpoint( + uuidGenerator uuidutil.UUIDGenerator, ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, @@ -72,6 +74,7 @@ func NewEndpoint( } return httprouter.Handle((&endpointDeps{ + uuidGenerator, ex, validator, requestsById, @@ -90,6 +93,7 @@ func NewEndpoint( } type endpointDeps struct { + uuidGenerator uuidutil.UUIDGenerator ex exchange.Exchange paramsValidator openrtb_ext.BidderParamValidator storedReqFetcher stored_requests.Fetcher @@ -1354,21 +1358,34 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson // Apply the Stored BidRequest, if it exists resolvedRequest := requestJson - if hasStoredBidRequest { - resolvedRequest, err = jsonpatch.MergePatch(storedRequests[storedBidRequestId], requestJson) + + if deps.cfg.GenerateRequestID { + isAppRequest, err := checkIfAppRequest(requestJson) if err != nil { - hasErr, Err := getJsonSyntaxError(requestJson) - if hasErr { - err = fmt.Errorf("Invalid JSON in Incoming Request: %s", Err) - } else { - hasErr, Err = getJsonSyntaxError(storedRequests[storedBidRequestId]) - if hasErr { - err = fmt.Errorf("Invalid JSON in Stored Request with ID %s: %s", storedBidRequestId, Err) - err = fmt.Errorf("ext.prebid.storedrequest.id refers to Stored Request %s which contains Invalid JSON: %s", storedBidRequestId, Err) - } - } return nil, nil, []error{err} } + if isAppRequest && hasStoredBidRequest { + uuidPatch, err := generateUuidForBidRequest(deps.uuidGenerator) + if err != nil { + return nil, nil, []error{err} + } + uuidPatch, err = jsonpatch.MergePatch(storedRequests[storedBidRequestId], uuidPatch) + if err != nil { + errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) + return nil, nil, errL + } + resolvedRequest, err = jsonpatch.MergePatch(requestJson, uuidPatch) + if err != nil { + errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) + return nil, nil, errL + } + } + } else if hasStoredBidRequest { + resolvedRequest, err = jsonpatch.MergePatch(storedRequests[storedBidRequestId], requestJson) + if err != nil { + errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) + return nil, nil, errL + } } // Apply default aliases, if they are provided @@ -1471,7 +1488,8 @@ type ImpExtPrebidData struct { // (e.g. malformed json, id not a string, etc). func getStoredRequestId(data []byte) (string, bool, error) { // These keys must be kept in sync with openrtb_ext.ExtStoredRequest - value, dataType, _, err := jsonparser.Get(data, "ext", openrtb_ext.PrebidExtKey, "storedrequest", "id") + storedRequestId, dataType, _, err := jsonparser.Get(data, "ext", openrtb_ext.PrebidExtKey, "storedrequest", "id") + if dataType == jsonparser.NotExist { return "", false, nil } @@ -1481,8 +1499,7 @@ func getStoredRequestId(data []byte) (string, bool, error) { if dataType != jsonparser.String { return "", true, errors.New("ext.prebid.storedrequest.id must be a string") } - - return string(value), true, nil + return string(storedRequestId), true, nil } // setIPImplicitly sets the IP address on bidReq, if it's not explicitly defined and we can figure it out. @@ -1584,3 +1601,35 @@ func getAccountID(pub *openrtb2.Publisher) string { } return metrics.PublisherUnknown } + +func storedRequestErrorChecker(requestJson []byte, storedRequests map[string]json.RawMessage, storedBidRequestId string) []error { + if hasErr, syntaxErr := getJsonSyntaxError(requestJson); hasErr { + return []error{fmt.Errorf("Invalid JSON in Incoming Request: %s", syntaxErr)} + } + if hasErr, syntaxErr := getJsonSyntaxError(storedRequests[storedBidRequestId]); hasErr { + return []error{fmt.Errorf("ext.prebid.storedrequest.id refers to Stored Request %s which contains Invalid JSON: %s", storedBidRequestId, syntaxErr)} + } + return nil +} + +func generateUuidForBidRequest(uuidGenerator uuidutil.UUIDGenerator) ([]byte, error) { + newBidRequestID, err := uuidGenerator.Generate() + if err != nil { + return nil, err + } + return []byte(`{"id":"` + newBidRequestID + `"}`), nil +} + +func checkIfAppRequest(request []byte) (bool, error) { + requestApp, dataType, _, err := jsonparser.Get(request, "app") + if dataType == jsonparser.NotExist { + return false, nil + } + if err != nil { + return false, err + } + if requestApp != nil { + return true, nil + } + return false, nil +} diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index ecc0554f45a..f2e0fb2a9f2 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -87,6 +87,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { ) endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, exchange, paramValidator, empty_fetcher.EmptyFetcher{}, diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 31c3c10951f..ebf1899436e 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -430,6 +430,7 @@ func TestExplicitUserId(t *testing.T) { }) endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, ex, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, @@ -464,6 +465,7 @@ func doRequest(t *testing.T, test testCase) (int, string) { mockExchange := newMockBidExchange(test.Config.MockBidder, test.Config.CurrencyRates) endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, mockExchange, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -534,6 +536,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -584,6 +587,7 @@ func TestNilExchange(t *testing.T) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. _, err := NewEndpoint( + fakeUUIDGenerator{}, nil, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, @@ -604,6 +608,7 @@ func TestNilValidator(t *testing.T) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. _, err := NewEndpoint( + fakeUUIDGenerator{}, &nobidExchange{}, nil, empty_fetcher.EmptyFetcher{}, @@ -625,6 +630,7 @@ func TestExchangeError(t *testing.T) { // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, &brokenExchange{}, newParamsValidator(t), empty_fetcher.EmptyFetcher{}, @@ -747,6 +753,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) { }, } endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, exchange, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -941,6 +948,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) { for _, test := range testCases { exchange := &nobidExchange{} endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, exchange, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1094,6 +1102,7 @@ func TestParseImpInfoMultipleImpressions(t *testing.T) { // Test the stored request functionality func TestStoredRequests(t *testing.T) { deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1140,10 +1149,81 @@ func TestStoredRequests(t *testing.T) { } } +func TestStoredRequestGenerateUuid(t *testing.T) { + uuid := "foo" + + deps := &endpointDeps{ + fakeUUIDGenerator{id: "foo", err: nil}, + &nobidExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + &metricsConfig.DummyMetricsEngine{}, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + req := &openrtb2.BidRequest{} + + testCases := []struct { + description string + givenRawData string + givenGenerateRequestID bool + expectedID string + }{ + { + description: "GenerateRequestID is true, rawData is an app request, and stored bid we should generate uuid", + givenRawData: testStoredRequestsUuid[2], + givenGenerateRequestID: true, + expectedID: uuid, + }, + { + description: "GenerateRequestID is true, rawData is an app request, but no stored bid, we should not generate uuid", + givenRawData: testStoredRequestsUuid[0], + givenGenerateRequestID: true, + expectedID: "ThisID", + }, + { + description: "GenerateRequestID is false so we should not generate uuid", + givenRawData: testStoredRequestsUuid[0], + givenGenerateRequestID: false, + expectedID: "ThisID", + }, + { + description: "GenerateRequestID is true, but rawData is a site request, we should not generate uuid", + givenRawData: testStoredRequestsUuid[1], + givenGenerateRequestID: true, + expectedID: "ThisID", + }, + } + + for _, test := range testCases { + deps.cfg.GenerateRequestID = test.givenGenerateRequestID + impInfo, errs := parseImpInfo([]byte(test.givenRawData)) + assert.Empty(t, errs, test.description) + newRequest, _, errList := deps.processStoredRequests(context.Background(), json.RawMessage(test.givenRawData), impInfo) + assert.Empty(t, errList, test.description) + + if err := json.Unmarshal(newRequest, req); err != nil { + t.Errorf("processStoredRequests Error: %s", err.Error()) + } + assert.Equalf(t, test.expectedID, req.ID, "The Bid Request ID is incorrect: %s\n", test.description) + } +} + // TestOversizedRequest makes sure we behave properly when the request size exceeds the configured max. func TestOversizedRequest(t *testing.T) { reqBody := validRequest(t, "site.json") deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1179,6 +1259,7 @@ func TestOversizedRequest(t *testing.T) { func TestRequestSizeEdgeCase(t *testing.T) { reqBody := validRequest(t, "site.json") deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1213,6 +1294,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { // TestNoEncoding prevents #231. func TestNoEncoding(t *testing.T) { endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, &mockExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1288,6 +1370,7 @@ func TestExplicitAMP(t *testing.T) { // TestContentType prevents #328 func TestContentType(t *testing.T) { endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, &mockExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1607,6 +1690,7 @@ func TestValidateImpExt(t *testing.T) { } deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1653,6 +1737,7 @@ func validRequest(t *testing.T, filename string) string { func TestCurrencyTrunc(t *testing.T) { deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1697,6 +1782,7 @@ func TestCurrencyTrunc(t *testing.T) { func TestCCPAInvalid(t *testing.T) { deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1745,6 +1831,7 @@ func TestCCPAInvalid(t *testing.T) { func TestNoSaleInvalid(t *testing.T) { deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1796,6 +1883,7 @@ func TestValidateSourceTID(t *testing.T) { } deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -1837,6 +1925,7 @@ func TestValidateSourceTID(t *testing.T) { func TestSChainInvalid(t *testing.T) { deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -2056,6 +2145,7 @@ func TestValidateAndFillSourceTID(t *testing.T) { func TestEidPermissionsInvalid(t *testing.T) { deps := &endpointDeps{ + fakeUUIDGenerator{}, &nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -2307,6 +2397,7 @@ func TestIOS14EndToEnd(t *testing.T) { exchange := &nobidExchange{} endpoint, _ := NewEndpoint( + fakeUUIDGenerator{}, exchange, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -2334,6 +2425,7 @@ func TestIOS14EndToEnd(t *testing.T) { func TestAuctionWarnings(t *testing.T) { reqBody := validRequest(t, "us-privacy-invalid.json") deps := &endpointDeps{ + fakeUUIDGenerator{}, &warningsCheckExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -2373,6 +2465,7 @@ func TestAuctionWarnings(t *testing.T) { func TestParseRequestParseImpInfoError(t *testing.T) { reqBody := validRequest(t, "imp-info-invalid.json") deps := &endpointDeps{ + fakeUUIDGenerator{}, &warningsCheckExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, @@ -3452,6 +3545,105 @@ var testStoredImps = []string{ ``, } +var testStoredRequestsUuid = []string{ + `{ + "id": "ThisID", + "app": { + "id": "123" + }, + "imp": [ + { + "video":{ + "h":300, + "w":200 + }, + "ext": { + "prebid": { + "storedrequest": { + "id": "1" + }, + "options": { + "echovideoattrs": true + } + } + } + } + ], + "ext": { + "prebid": { + "cache": { + "markup": 1 + }, + "targeting": { + } + } + } + }`, + `{ + "id": "ThisID", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "adUnit2", + "ext": { + "prebid": { + "storedrequest": { + "id": "1" + }, + "options": { + "echovideoattrs": true + } + }, + "appnexus": { + "placementId": "def", + "trafficSourceCode": "mysite.com", + "reserve": null + }, + "rubicon": null + } + } + ], + "ext": { + "prebid": { + "cache": { + "markup": 1 + }, + "targeting": { + } + } + } + }`, + `{ + "id": "ThisID", + "app": { + "id": "123" + }, + "imp": [ + { + "ext": { + "prebid": { + "storedrequest": { + "id": "2" + }, + "options": { + "echovideoattrs": false + } + } + } + } + ], + "ext": { + "prebid": { + "storedrequest": { + "id": "2" + } + } + } + }`, +} + type mockStoredReqFetcher struct { } @@ -3514,3 +3706,12 @@ type hardcodedResponseIPValidator struct { func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool { return v.response } + +type fakeUUIDGenerator struct { + id string + err error +} + +func (f fakeUUIDGenerator) Generate() (string, error) { + return f.id, f.err +} diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index c7e3dfbde3e..56c67c4dd97 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -20,6 +20,7 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/uuidutil" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -37,6 +38,7 @@ import ( var defaultRequestTimeout int64 = 5000 func NewVideoEndpoint( + uuidGenerator uuidutil.UUIDGenerator, ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, @@ -65,6 +67,7 @@ func NewVideoEndpoint( videoEndpointRegexp := regexp.MustCompile(`[<>]`) return httprouter.Handle((&endpointDeps{ + uuidGenerator, ex, validator, requestsById, diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index b00467d95c3..26d9f028885 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1210,6 +1210,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m metrics := metrics.NewMetrics(gometrics.NewRegistry(), openrtb_ext.CoreBidderNames(), config.DisabledMetrics{}, nil) deps := &endpointDeps{ + fakeUUIDGenerator{}, ex, newParamsValidator(t), &mockVideoStoredReqFetcher{}, @@ -1252,6 +1253,7 @@ func (m *mockAnalyticsModule) LogNotificationEventObject(ne *analytics.Notificat func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { deps := &endpointDeps{ + fakeUUIDGenerator{}, ex, newParamsValidator(t), &mockVideoStoredReqFetcher{}, @@ -1274,6 +1276,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) *endpointDeps { deps := &endpointDeps{ + fakeUUIDGenerator{}, ex, newParamsValidator(t), &mockVideoStoredReqFetcher{}, @@ -1296,6 +1299,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { edep := &endpointDeps{ + fakeUUIDGenerator{}, ex, newParamsValidator(t), &mockVideoStoredReqFetcher{}, diff --git a/router/router.go b/router/router.go index 446d4280b09..73265920107 100644 --- a/router/router.go +++ b/router/router.go @@ -12,6 +12,13 @@ import ( "strings" "time" + "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/endpoints/events" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/util/uuidutil" + + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/appnexus" @@ -27,15 +34,11 @@ import ( "github.com/prebid/prebid-server/cache/filecache" "github.com/prebid/prebid-server/cache/postgrescache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/endpoints" - "github.com/prebid/prebid-server/endpoints/events" infoEndpoints "github.com/prebid/prebid-server/endpoints/info" "github.com/prebid/prebid-server/endpoints/openrtb2" - "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" @@ -276,18 +279,18 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor, categoriesFetcher) - - openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) + var uuidGenerator uuidutil.UUIDRandomGenerator + openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) if err != nil { glog.Fatalf("Failed to create the openrtb2 endpoint handler. %v", err) } - ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) + ampEndpoint, err := openrtb2.NewAmpEndpoint(uuidGenerator, theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) if err != nil { glog.Fatalf("Failed to create the amp endpoint handler. %v", err) } - videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, cacheClient) + videoEndpoint, err := openrtb2.NewVideoEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, cacheClient) if err != nil { glog.Fatalf("Failed to create the video endpoint handler. %v", err) } diff --git a/util/uuidutil/uuidutil.go b/util/uuidutil/uuidutil.go new file mode 100644 index 00000000000..6e4848f62b9 --- /dev/null +++ b/util/uuidutil/uuidutil.go @@ -0,0 +1,19 @@ +package uuidutil + +import ( + "github.com/gofrs/uuid" +) + +type UUIDGenerator interface { + Generate() (string, error) +} + +type UUIDRandomGenerator struct{} + +func (UUIDRandomGenerator) Generate() (string, error) { + id, err := uuid.NewV4() + if err != nil { + return "", err + } + return id.String(), nil +} From 48fcdfb3fe864044b18575b14cbb671dff89b94f Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 15 Sep 2021 00:30:40 -0400 Subject: [PATCH 089/140] Fix UserSync Config Case Sensitivity Issue (#1996) --- router/router.go | 4 +++- router/router_test.go | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/router/router.go b/router/router.go index 73265920107..17145b5a1e7 100644 --- a/router/router.go +++ b/router/router.go @@ -340,7 +340,9 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R func applyBidderInfoConfigOverrides(bidderInfos config.BidderInfos, adaptersCfg map[string]config.Adapter) error { for bidderName, bidderInfo := range bidderInfos { - if adapterCfg, exists := adaptersCfg[bidderName]; exists { + // bidder name from bidderInfos is case-sensitive, but bidder name from adaptersCfg + // is always expressed as lower case. need to adapt for the difference here. + if adapterCfg, exists := adaptersCfg[strings.ToLower(bidderName)]; exists { bidderInfo.Syncer = adapterCfg.Syncer.Override(bidderInfo.Syncer) // validate and try to apply the legacy usersync_url configuration in attempt to provide diff --git a/router/router_test.go b/router/router_test.go index 07e5938800e..24a7709c365 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -88,6 +88,14 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { givenAdaptersCfg: map[string]config.Adapter{"a": {Syncer: &config.Syncer{Key: "override"}}}, expectedBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{Key: "override"}}}, }, + { + // Adapter Configs use a lower case bidder name, but the Bidder Infos follow the official + // bidder name casing. + description: "Syncer Override - Case Sensitivity", + givenBidderInfos: config.BidderInfos{"A": {Syncer: &config.Syncer{Key: "original"}}}, + givenAdaptersCfg: map[string]config.Adapter{"a": {Syncer: &config.Syncer{Key: "override"}}}, + expectedBidderInfos: config.BidderInfos{"A": {Syncer: &config.Syncer{Key: "override"}}}, + }, { description: "UserSyncURL Override IFrame", givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{IFrame: &config.SyncerEndpoint{URL: "original"}}}}, From e1299058fefd07ae90333d35a98d371aa2439609 Mon Sep 17 00:00:00 2001 From: IQZoneAdx <88879712+IQZoneAdx@users.noreply.github.com> Date: Wed, 15 Sep 2021 21:32:34 +0300 Subject: [PATCH 090/140] IQzone Bidder Adapter: add new bid param (#1987) add IQZone adapter --- adapters/iqzone/iqzone.go | 80 ++++++++--- .../iqzonetest/exemplary/endpointId.json | 133 ++++++++++++++++++ .../iqzonetest/exemplary/simple-banner.json | 3 +- .../iqzonetest/exemplary/simple-native.json | 3 +- .../iqzonetest/exemplary/simple-video.json | 3 +- .../exemplary/simple-web-banner.json | 3 +- .../supplemental/bad_media_type.json | 3 +- .../iqzonetest/supplemental/bad_response.json | 3 +- .../iqzonetest/supplemental/status-204.json | 3 +- .../supplemental/status-not-200.json | 5 +- adapters/iqzone/params_test.go | 6 +- openrtb_ext/imp_iqzone.go | 1 + static/bidder-params/iqzone.json | 14 +- 13 files changed, 229 insertions(+), 31 deletions(-) create mode 100644 adapters/iqzone/iqzonetest/exemplary/endpointId.json diff --git a/adapters/iqzone/iqzone.go b/adapters/iqzone/iqzone.go index 190466b36e0..79dc04d835a 100644 --- a/adapters/iqzone/iqzone.go +++ b/adapters/iqzone/iqzone.go @@ -23,19 +23,71 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -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} +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errs []error + var err error + var adapterRequests []*adapters.RequestData + + reqCopy := *request + for _, imp := range request.Imp { + reqCopy.Imp = []openrtb2.Imp{imp} + + var bidderExt adapters.ExtImpBidder + var iqzoneExt openrtb_ext.ImpExtIQZone + + if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { + return nil, append(errs, err) + } + if err = json.Unmarshal(bidderExt.Bidder, &iqzoneExt); err != nil { + return nil, append(errs, err) + } + + finalyImpExt := reqCopy.Imp[0].Ext + if iqzoneExt.PlacementID != "" { + finalyImpExt, _ = json.Marshal(map[string]interface{}{ + "bidder": map[string]interface{}{ + "placementId": iqzoneExt.PlacementID, + "type": "publisher", + }, + }) + } else if iqzoneExt.EndpointID != "" { + finalyImpExt, _ = json.Marshal(map[string]interface{}{ + "bidder": map[string]interface{}{ + "endpointId": iqzoneExt.EndpointID, + "type": "network", + }, + }) + } + + reqCopy.Imp[0].Ext = finalyImpExt + + adapterReq, errors := a.makeRequest(&reqCopy) + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + errs = append(errs, errors...) } + return adapterRequests, errs +} + +func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { + var errs []error - requestData := &adapters.RequestData{ - Method: "POST", - Uri: a.endpoint, - Body: requestJSON, + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs } - return []*adapters.RequestData{requestData}, nil + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }, errs } func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { @@ -75,20 +127,16 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - var mediaType openrtb_ext.BidType = "" for _, imp := range imps { if imp.ID == impID { if imp.Banner != nil { - mediaType = openrtb_ext.BidTypeBanner - return mediaType, nil + return openrtb_ext.BidTypeBanner, nil } if imp.Banner == nil && imp.Video != nil { - mediaType = openrtb_ext.BidTypeVideo - return mediaType, nil + return openrtb_ext.BidTypeVideo, nil } if imp.Banner == nil && imp.Video == nil && imp.Native != nil { - mediaType = openrtb_ext.BidTypeNative - return mediaType, nil + return openrtb_ext.BidTypeNative, nil } } } diff --git a/adapters/iqzone/iqzonetest/exemplary/endpointId.json b/adapters/iqzone/iqzonetest/exemplary/endpointId.json new file mode 100644 index 00000000000..79887443a89 --- /dev/null +++ b/adapters/iqzone/iqzonetest/exemplary/endpointId.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://smartssp-us-east.iqzone.com/pserver", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "tagid": "test", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "endpointId": "test", + "type": "network" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ua": "iPad" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "iqzone" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 300, + "h": 250, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/iqzone/iqzonetest/exemplary/simple-banner.json b/adapters/iqzone/iqzonetest/exemplary/simple-banner.json index f816191ec32..12f97e1bb52 100644 --- a/adapters/iqzone/iqzonetest/exemplary/simple-banner.json +++ b/adapters/iqzone/iqzonetest/exemplary/simple-banner.json @@ -57,7 +57,8 @@ }, "ext": { "bidder": { - "placementId": "test" + "placementId": "test", + "type": "publisher" } } } diff --git a/adapters/iqzone/iqzonetest/exemplary/simple-native.json b/adapters/iqzone/iqzonetest/exemplary/simple-native.json index 819e14bf572..967a846dd07 100644 --- a/adapters/iqzone/iqzonetest/exemplary/simple-native.json +++ b/adapters/iqzone/iqzonetest/exemplary/simple-native.json @@ -41,7 +41,8 @@ }, "ext": { "bidder": { - "placementId": "test" + "placementId": "test", + "type": "publisher" } } } diff --git a/adapters/iqzone/iqzonetest/exemplary/simple-video.json b/adapters/iqzone/iqzonetest/exemplary/simple-video.json index 16e6f4c7144..7e2b7971e24 100644 --- a/adapters/iqzone/iqzonetest/exemplary/simple-video.json +++ b/adapters/iqzone/iqzonetest/exemplary/simple-video.json @@ -63,7 +63,8 @@ }, "ext": { "bidder": { - "placementId": "test" + "placementId": "test", + "type": "publisher" } } } diff --git a/adapters/iqzone/iqzonetest/exemplary/simple-web-banner.json b/adapters/iqzone/iqzonetest/exemplary/simple-web-banner.json index 968c030575b..4d69ef41336 100644 --- a/adapters/iqzone/iqzonetest/exemplary/simple-web-banner.json +++ b/adapters/iqzone/iqzonetest/exemplary/simple-web-banner.json @@ -57,7 +57,8 @@ }, "ext": { "bidder": { - "placementId": "test" + "placementId": "test", + "type": "publisher" } } } diff --git a/adapters/iqzone/iqzonetest/supplemental/bad_media_type.json b/adapters/iqzone/iqzonetest/supplemental/bad_media_type.json index 3d1923104e7..4dddd568932 100644 --- a/adapters/iqzone/iqzonetest/supplemental/bad_media_type.json +++ b/adapters/iqzone/iqzonetest/supplemental/bad_media_type.json @@ -30,7 +30,8 @@ "id": "test-imp-id", "ext": { "bidder": { - "placementId": "test" + "placementId": "test", + "type": "publisher" } } } diff --git a/adapters/iqzone/iqzonetest/supplemental/bad_response.json b/adapters/iqzone/iqzonetest/supplemental/bad_response.json index ad420705fcd..74c603b4a9e 100644 --- a/adapters/iqzone/iqzonetest/supplemental/bad_response.json +++ b/adapters/iqzone/iqzonetest/supplemental/bad_response.json @@ -54,7 +54,8 @@ }, "ext": { "bidder": { - "placementId": "test" + "placementId": "test", + "type": "publisher" } } } diff --git a/adapters/iqzone/iqzonetest/supplemental/status-204.json b/adapters/iqzone/iqzonetest/supplemental/status-204.json index 7c2f21c0aa7..8ef22bdaad6 100644 --- a/adapters/iqzone/iqzonetest/supplemental/status-204.json +++ b/adapters/iqzone/iqzonetest/supplemental/status-204.json @@ -54,7 +54,8 @@ }, "ext": { "bidder": { - "placementId": "test" + "placementId": "test", + "type": "publisher" } } } diff --git a/adapters/iqzone/iqzonetest/supplemental/status-not-200.json b/adapters/iqzone/iqzonetest/supplemental/status-not-200.json index 22160ac78d3..2cb4ba98ecc 100644 --- a/adapters/iqzone/iqzonetest/supplemental/status-not-200.json +++ b/adapters/iqzone/iqzonetest/supplemental/status-not-200.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "TagID": "test" + "placementId": "test" } } } @@ -54,7 +54,8 @@ }, "ext": { "bidder": { - "TagID": "test" + "placementId": "test", + "type": "publisher" } } } diff --git a/adapters/iqzone/params_test.go b/adapters/iqzone/params_test.go index deb4af2eda6..c06f5e7fab4 100644 --- a/adapters/iqzone/params_test.go +++ b/adapters/iqzone/params_test.go @@ -36,10 +36,12 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"placementId": "test"}`, `{"placementId": "1"}`, + `{"endpointId": "test"}`, + `{"endpointId": "1"}`, } var invalidParams = []string{ `{"placementId": 42}`, - `{}`, - `{"someOtherParam": "value"}`, + `{"endpointId": 42}`, + `{"placementId": "1", "endpointId": "1"}`, } diff --git a/openrtb_ext/imp_iqzone.go b/openrtb_ext/imp_iqzone.go index 67a03376067..e885625c6f6 100644 --- a/openrtb_ext/imp_iqzone.go +++ b/openrtb_ext/imp_iqzone.go @@ -2,4 +2,5 @@ package openrtb_ext type ImpExtIQZone struct { PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` } diff --git a/static/bidder-params/iqzone.json b/static/bidder-params/iqzone.json index 38e98c53346..5113dfafd1d 100644 --- a/static/bidder-params/iqzone.json +++ b/static/bidder-params/iqzone.json @@ -1,15 +1,21 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "IQZone Adapter Params", - "description": "A schema which validates params accepted by the IQZone adapter", + "title": "IQzone Adapter Params", + "description": "A schema which validates params accepted by the IQzone adapter", "type": "object", "properties": { "placementId": { "type": "string", "description": "Placement ID" + }, + "endpointId": { + "type": "string", + "description": "Endpoint ID" } }, - - "required": ["placementId"] + "oneOf": [ + { "required": ["placementId"] }, + { "required": ["endpointId"] } + ] } \ No newline at end of file From a02f5c24aaa05f69aa6254a841ef7eefe1696d07 Mon Sep 17 00:00:00 2001 From: savarino-o <90327117+savarino-o@users.noreply.github.com> Date: Wed, 15 Sep 2021 20:33:04 +0200 Subject: [PATCH 091/140] Adot: Add Usersync (#1995) --- static/bidder-info/adot.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/bidder-info/adot.yaml b/static/bidder-info/adot.yaml index 529ff3ae4cb..6a7aca06881 100644 --- a/static/bidder-info/adot.yaml +++ b/static/bidder-info/adot.yaml @@ -12,3 +12,7 @@ capabilities: - banner - video - native +userSync: + redirect: + url: "https://sync.adotmob.com/cookie/pbs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}" + userMacro: "{amob_user_id}" \ No newline at end of file From a1e5306954961e4570333059f8a0abd65554762c Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 15 Sep 2021 14:53:54 -0400 Subject: [PATCH 092/140] /info/bidders endpoint consistency (#1990) --- endpoints/info/bidders.go | 68 +++++++++-- endpoints/info/bidders_test.go | 210 +++++++++++++++++++++++++++++---- 2 files changed, 246 insertions(+), 32 deletions(-) diff --git a/endpoints/info/bidders.go b/endpoints/info/bidders.go index 2bd925ce62d..54a9104ec81 100644 --- a/endpoints/info/bidders.go +++ b/endpoints/info/bidders.go @@ -4,28 +4,62 @@ import ( "encoding/json" "net/http" "sort" + "strings" "github.com/golang/glog" "github.com/julienschmidt/httprouter" "github.com/prebid/prebid-server/config" ) +var invalidEnabledOnly = []byte(`Invalid value for 'enabledonly' query param, must be of boolean type`) + // NewBiddersEndpoint builds a handler for the /info/bidders endpoint. func NewBiddersEndpoint(bidders config.BidderInfos, aliases map[string]string) httprouter.Handle { - response, err := prepareBiddersResponse(bidders, aliases) + responseAll, err := prepareBiddersResponseAll(bidders, aliases) + if err != nil { + glog.Fatalf("error creating /info/bidders endpoint all bidders response: %v", err) + } + + responseEnabledOnly, err := prepareBiddersResponseEnabledOnly(bidders, aliases) if err != nil { - glog.Fatalf("error creating /info/bidders endpoint response: %v", err) + glog.Fatalf("error creating /info/bidders endpoint enabled only response: %v", err) } - return func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { - w.Header().Set("Content-Type", "application/json") - if _, err := w.Write(response); err != nil { - glog.Errorf("error writing response to /info/bidders: %v", err) + return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + var writeErr error + switch readEnabledOnly(r) { + case "true": + w.Header().Set("Content-Type", "application/json") + _, writeErr = w.Write(responseEnabledOnly) + case "false": + w.Header().Set("Content-Type", "application/json") + _, writeErr = w.Write(responseAll) + default: + w.WriteHeader(http.StatusBadRequest) + _, writeErr = w.Write(invalidEnabledOnly) + } + + if writeErr != nil { + glog.Errorf("error writing response to /info/bidders: %v", writeErr) } } } -func prepareBiddersResponse(bidders config.BidderInfos, aliases map[string]string) ([]byte, error) { +func readEnabledOnly(r *http.Request) string { + q := r.URL.Query() + + v, exists := q["enabledonly"] + + if !exists || len(v) == 0 { + // if the enabledOnly query parameter is not specified, default to false to match + // previous behavior of returning all adapters regardless of their enabled status. + return "false" + } + + return strings.ToLower(v[0]) +} + +func prepareBiddersResponseAll(bidders config.BidderInfos, aliases map[string]string) ([]byte, error) { bidderNames := make([]string, 0, len(bidders)+len(aliases)) for name := range bidders { @@ -40,3 +74,23 @@ func prepareBiddersResponse(bidders config.BidderInfos, aliases map[string]strin return json.Marshal(bidderNames) } + +func prepareBiddersResponseEnabledOnly(bidders config.BidderInfos, aliases map[string]string) ([]byte, error) { + bidderNames := make([]string, 0, len(bidders)+len(aliases)) + + for name, info := range bidders { + if info.Enabled { + bidderNames = append(bidderNames, name) + } + } + + for name, bidder := range aliases { + if info, ok := bidders[bidder]; ok && info.Enabled { + bidderNames = append(bidderNames, name) + } + } + + sort.Strings(bidderNames) + + return json.Marshal(bidderNames) +} diff --git a/endpoints/info/bidders_test.go b/endpoints/info/bidders_test.go index e48dd3d0e8e..7110e1889e5 100644 --- a/endpoints/info/bidders_test.go +++ b/endpoints/info/bidders_test.go @@ -10,7 +10,12 @@ import ( "github.com/stretchr/testify/assert" ) -func TestPrepareBiddersResponse(t *testing.T) { +func TestPrepareBiddersResponseAll(t *testing.T) { + var ( + enabled = config.BidderInfo{Enabled: true} + disabled = config.BidderInfo{Enabled: false} + ) + testCases := []struct { description string givenBidders config.BidderInfos @@ -24,45 +29,133 @@ func TestPrepareBiddersResponse(t *testing.T) { expected: `[]`, }, { - description: "Core Bidders Only - One", - givenBidders: config.BidderInfos{"a": {}}, + description: "Core Bidders Only - One - Enabled", + givenBidders: config.BidderInfos{"a": enabled}, + givenAliases: nil, + expected: `["a"]`, + }, + { + description: "Core Bidders Only - One - Disabled", + givenBidders: config.BidderInfos{"a": disabled}, givenAliases: nil, expected: `["a"]`, }, { description: "Core Bidders Only - Many", - givenBidders: config.BidderInfos{"a": {}, "b": {}}, + givenBidders: config.BidderInfos{"a": enabled, "b": enabled}, givenAliases: nil, expected: `["a","b"]`, }, { - description: "Core Bidders Only - Many Sorted", - givenBidders: config.BidderInfos{"z": {}, "a": {}}, + description: "Core Bidders Only - Many - Mixed", + givenBidders: config.BidderInfos{"a": disabled, "b": enabled}, givenAliases: nil, + expected: `["a","b"]`, + }, + { + description: "Core Bidders Only - Many - Sorted", + givenBidders: config.BidderInfos{"b": enabled, "a": enabled}, + givenAliases: nil, + expected: `["a","b"]`, + }, + { + description: "With Aliases - One", + givenBidders: config.BidderInfos{"a": enabled}, + givenAliases: map[string]string{"b": "a"}, + expected: `["a","b"]`, + }, + { + description: "With Aliases - Many", + givenBidders: config.BidderInfos{"a": enabled, "b": disabled}, + givenAliases: map[string]string{"x": "a", "y": "b"}, + expected: `["a","b","x","y"]`, + }, + { + description: "With Aliases - Sorted", + givenBidders: config.BidderInfos{"z": enabled}, + givenAliases: map[string]string{"a": "z"}, expected: `["a","z"]`, }, + } + + for _, test := range testCases { + result, err := prepareBiddersResponseAll(test.givenBidders, test.givenAliases) + + assert.NoError(t, err, test.description) + assert.Equal(t, []byte(test.expected), result, test.description) + } +} + +func TestPrepareBiddersResponseEnabledOnly(t *testing.T) { + var ( + enabled = config.BidderInfo{Enabled: true} + disabled = config.BidderInfo{Enabled: false} + ) + + testCases := []struct { + description string + givenBidders config.BidderInfos + givenAliases map[string]string + expected string + }{ + { + description: "None", + givenBidders: config.BidderInfos{}, + givenAliases: nil, + expected: `[]`, + }, + { + description: "Core Bidders Only - One - Enabled", + givenBidders: config.BidderInfos{"a": enabled}, + givenAliases: nil, + expected: `["a"]`, + }, + { + description: "Core Bidders Only - One - Disabled", + givenBidders: config.BidderInfos{"a": disabled}, + givenAliases: nil, + expected: `[]`, + }, + { + description: "Core Bidders Only - Many", + givenBidders: config.BidderInfos{"a": enabled, "b": enabled}, + givenAliases: nil, + expected: `["a","b"]`, + }, + { + description: "Core Bidders Only - Many - Mixed", + givenBidders: config.BidderInfos{"a": disabled, "b": enabled}, + givenAliases: nil, + expected: `["b"]`, + }, + { + description: "Core Bidders Only - Many - Sorted", + givenBidders: config.BidderInfos{"b": enabled, "a": enabled}, + givenAliases: nil, + expected: `["a","b"]`, + }, { description: "With Aliases - One", - givenBidders: config.BidderInfos{"a": {}}, - givenAliases: map[string]string{"b": "b"}, + givenBidders: config.BidderInfos{"a": enabled}, + givenAliases: map[string]string{"b": "a"}, expected: `["a","b"]`, }, { description: "With Aliases - Many", - givenBidders: config.BidderInfos{"a": {}}, - givenAliases: map[string]string{"b": "b", "c": "c"}, - expected: `["a","b","c"]`, + givenBidders: config.BidderInfos{"a": enabled, "b": disabled}, + givenAliases: map[string]string{"x": "a", "y": "b"}, + expected: `["a","x"]`, }, { description: "With Aliases - Sorted", - givenBidders: config.BidderInfos{"z": {}}, - givenAliases: map[string]string{"a": "a"}, + givenBidders: config.BidderInfos{"z": enabled}, + givenAliases: map[string]string{"a": "z"}, expected: `["a","z"]`, }, } for _, test := range testCases { - result, err := prepareBiddersResponse(test.givenBidders, test.givenAliases) + result, err := prepareBiddersResponseEnabledOnly(test.givenBidders, test.givenAliases) assert.NoError(t, err, test.description) assert.Equal(t, []byte(test.expected), result, test.description) @@ -70,20 +163,87 @@ func TestPrepareBiddersResponse(t *testing.T) { } func TestBiddersHandler(t *testing.T) { - bidders := config.BidderInfos{"a": {}} - aliases := map[string]string{"b": "b"} + var ( + enabled = config.BidderInfo{Enabled: true} + disabled = config.BidderInfo{Enabled: false} + ) + + bidders := config.BidderInfos{"a": enabled, "b": disabled} + aliases := map[string]string{"x": "a", "y": "b"} - handler := NewBiddersEndpoint(bidders, aliases) + testCases := []struct { + description string + givenURL string + expectedStatus int + expectedBody string + expectedHeaders http.Header + }{ + { + description: "No Query Paramters - Backwards Compatability", + givenURL: "/info/bidders", + expectedStatus: http.StatusOK, + expectedBody: `["a","b","x","y"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + description: "Enabled Only - False", + givenURL: "/info/bidders?enabledonly=false", + expectedStatus: http.StatusOK, + expectedBody: `["a","b","x","y"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + description: "Enabled Only - False - Case Insensitive", + givenURL: "/info/bidders?enabledonly=fAlSe", + expectedStatus: http.StatusOK, + expectedBody: `["a","b","x","y"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + description: "Enabled Only - True", + givenURL: "/info/bidders?enabledonly=true", + expectedStatus: http.StatusOK, + expectedBody: `["a","x"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + description: "Enabled Only - True - Case Insensitive", + givenURL: "/info/bidders?enabledonly=TrUe", + expectedStatus: http.StatusOK, + expectedBody: `["a","x"]`, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + }, + { + description: "Enabled Only - Invalid", + givenURL: "/info/bidders?enabledonly=foo", + expectedStatus: http.StatusBadRequest, + expectedBody: `Invalid value for 'enabledonly' query param, must be of boolean type`, + expectedHeaders: http.Header{}, + }, + { + description: "Enabled Only - Missing Value", + givenURL: "/info/bidders?enabledonly=", + expectedStatus: http.StatusBadRequest, + expectedBody: `Invalid value for 'enabledonly' query param, must be of boolean type`, + expectedHeaders: http.Header{}, + }, + } + + for _, test := range testCases { + handler := NewBiddersEndpoint(bidders, aliases) - responseRecorder := httptest.NewRecorder() - handler(responseRecorder, nil, nil) + request := httptest.NewRequest("GET", test.givenURL, nil) - result := responseRecorder.Result() - assert.Equal(t, result.StatusCode, http.StatusOK) + responseRecorder := httptest.NewRecorder() + handler(responseRecorder, request, nil) - resultBody, _ := ioutil.ReadAll(result.Body) - assert.Equal(t, []byte(`["a","b"]`), resultBody) + result := responseRecorder.Result() + assert.Equal(t, result.StatusCode, test.expectedStatus) - resultHeaders := result.Header - assert.Equal(t, http.Header{"Content-Type": []string{"application/json"}}, resultHeaders) + resultBody, _ := ioutil.ReadAll(result.Body) + assert.Equal(t, []byte(test.expectedBody), resultBody) + + resultHeaders := result.Header + assert.Equal(t, test.expectedHeaders, resultHeaders) + } } From ce3287a9ec91bbf75c7c5c48b5e6e5c2bd0050b0 Mon Sep 17 00:00:00 2001 From: BertiBauer <14088844+BertiBauer@users.noreply.github.com> Date: Mon, 20 Sep 2021 20:16:39 +0200 Subject: [PATCH 093/140] Yieldlab: Fix ad unit handling (#1962) * Fix wrong adunit for bid * Fix wrong adunit for bid * Add JSON test for multiple impressions * Add adslot to imp map --- adapters/yieldlab/yieldlab.go | 71 +++++--- .../exemplary/multiple_impressions.json | 156 ++++++++++++++++++ 2 files changed, 202 insertions(+), 25 deletions(-) create mode 100644 adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go index ee9170c25cf..66f12c8bcbe 100644 --- a/adapters/yieldlab/yieldlab.go +++ b/adapters/yieldlab/yieldlab.go @@ -212,7 +212,15 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa Bids: []*adapters.TypedBid{}, } - for i, bid := range bids { + adslotToImpMap := make(map[string]*openrtb2.Imp) + for i := 0; i < len(internalRequest.Imp); i++ { + adslotID := a.extractAdslotID(internalRequest.Imp[i]) + if internalRequest.Imp[i].Video != nil || internalRequest.Imp[i].Banner != nil { + adslotToImpMap[adslotID] = &internalRequest.Imp[i] + } + } + + for _, bid := range bids { width, height, err := splitSize(bid.Adsize) if err != nil { return nil, []error{err} @@ -225,33 +233,37 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa } } - var bidType openrtb_ext.BidType - responseBid := &openrtb2.Bid{ - ID: strconv.FormatUint(bid.ID, 10), - Price: float64(bid.Price) / 100, - ImpID: internalRequest.Imp[i].ID, - CrID: a.makeCreativeID(req, bid), - DealID: strconv.FormatUint(bid.Pid, 10), - W: int64(width), - H: int64(height), - } + if imp, exists := adslotToImpMap[strconv.FormatUint(bid.ID, 10)]; !exists { + continue + } else { + var bidType openrtb_ext.BidType + responseBid := &openrtb2.Bid{ + ID: strconv.FormatUint(bid.ID, 10), + Price: float64(bid.Price) / 100, + ImpID: imp.ID, + CrID: a.makeCreativeID(req, bid), + DealID: strconv.FormatUint(bid.Pid, 10), + W: int64(width), + H: int64(height), + } - if internalRequest.Imp[i].Video != nil { - bidType = openrtb_ext.BidTypeVideo - responseBid.NURL = a.makeAdSourceURL(internalRequest, req, bid) + if imp.Video != nil { + bidType = openrtb_ext.BidTypeVideo + responseBid.NURL = a.makeAdSourceURL(internalRequest, req, bid) - } else if internalRequest.Imp[i].Banner != nil { - bidType = openrtb_ext.BidTypeBanner - responseBid.AdM = a.makeBannerAdSource(internalRequest, req, bid) - } else { - // Yieldlab adapter currently doesn't support Audio and Native ads - continue - } + } else if imp.Banner != nil { + bidType = openrtb_ext.BidTypeBanner + responseBid.AdM = a.makeBannerAdSource(internalRequest, req, bid) + } else { + // Yieldlab adapter currently doesn't support Audio and Native ads + continue + } - bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ - BidType: bidType, - Bid: responseBid, - }) + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + BidType: bidType, + Bid: responseBid, + }) + } } return bidderResponse, nil @@ -259,6 +271,7 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtImpYieldlab) *openrtb_ext.ExtImpYieldlab { slotIdStr := strconv.FormatUint(adslotID, 10) + for _, p := range params { if p.AdslotID == slotIdStr { return p @@ -268,6 +281,14 @@ func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtI return nil } +func (a *YieldlabAdapter) extractAdslotID(internalRequestImp openrtb2.Imp) string { + bidderExt := new(adapters.ExtImpBidder) + json.Unmarshal(internalRequestImp.Ext, bidderExt) + yieldlabExt := new(openrtb_ext.ExtImpYieldlab) + json.Unmarshal(bidderExt.Bidder, yieldlabExt) + return yieldlabExt.AdslotID +} + func (a *YieldlabAdapter) makeBannerAdSource(req *openrtb2.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { return fmt.Sprintf(adSourceBanner, a.makeAdSourceURL(req, ext, res)) } diff --git a/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json b/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json new file mode 100644 index 00000000000..c68748e39f4 --- /dev/null +++ b/adapters/yieldlab/yieldlabtest/exemplary/multiple_impressions.json @@ -0,0 +1,156 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "12345", + "supplyId": "123456789", + "adSize": "728x90", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + }, + { + "id": "test-imp-id2", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adslotId": "67890", + "supplyId": "123456789", + "adSize": "300x250", + "targeting": { + "key1": "value1", + "key2": "value2" + }, + "extId": "abc" + } + } + } + ], + "user": { + "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c" + }, + "device": { + "ifa": "hello-ads", + "devicetype": 4, + "connectiontype": 6, + "geo": { + "lat": 51.499488, + "lon": -0.128953 + }, + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", + "ip": "169.254.13.37", + "h": 1098, + "w": 814 + }, + "site": { + "id": "fake-site-id", + "publisher": { + "id": "1" + }, + "page": "http://localhost:9090/gdpr.html" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Accept": [ + "application/json" + ], + "Cookie": [ + "id=34a53e82-0dc3-4815-8b7e-b725ede0361c" + ], + "Referer": [ + "http://localhost:9090/gdpr.html" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" + ], + "X-Forwarded-For": [ + "169.254.13.37" + ] + }, + "uri": "https://ad.yieldlab.net/testing/12345,67890?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads" + }, + "mockResponse": { + "status": 200, + "body": [ + { + "id": 12345, + "price": 201, + "advertiser": "yieldlab", + "adsize": "728x90", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5" + }, + { + "id": 67890, + "price": 131, + "advertiser": "yieldlab", + "adsize": "300x250", + "pid": 1234, + "did": 5678, + "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5" + } + ] + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "adm": "", + "crid": "12345123433", + "dealid": "1234", + "id": "12345", + "impid": "test-imp-id", + "price": 2.01, + "w": 728, + "h": 90 + }, + "type": "banner" + }, + { + "bid": { + "adm": "", + "crid": "67890123433", + "dealid": "1234", + "id": "67890", + "impid": "test-imp-id2", + "price": 1.31, + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} From ecbe8f8bce8cf53ac001fd855ef16d8eede73d33 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 20 Sep 2021 15:44:52 -0400 Subject: [PATCH 094/140] Fix GDPR EEACountries (#2003) --- config/config.go | 6 +++--- config/config_test.go | 22 ++++++++-------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/config/config.go b/config/config.go index f6f6f187602..a8d85f1fe76 100644 --- a/config/config.go +++ b/config/config.go @@ -511,9 +511,9 @@ func New(v *viper.Viper) (*Configuration, error) { c.GDPR.NonStandardPublisherMap[c.GDPR.NonStandardPublishers[i]] = s } - c.GDPR.EEACountriesMap = make(map[string]struct{}) - for i := 0; i < len(c.GDPR.EEACountriesMap); i++ { - c.GDPR.NonStandardPublisherMap[c.GDPR.EEACountries[i]] = s + c.GDPR.EEACountriesMap = make(map[string]struct{}, len(c.GDPR.EEACountries)) + for _, v := range c.GDPR.EEACountries { + c.GDPR.EEACountriesMap[v] = s } // To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table located in the diff --git a/config/config_test.go b/config/config_test.go index b22b384d9bd..03ad88461e2 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -228,7 +228,8 @@ var fullConfig = []byte(` gdpr: host_vendor_id: 15 default_value: "1" - non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] + non_standard_publishers: ["pub1", "pub2"] + eea_countries: ["eea1", "eea2"] tcf2: purpose1: enforce_vendors: false @@ -440,19 +441,12 @@ func TestFullConfig(t *testing.T) { 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") - cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[1], "fake-site-id") - cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[2], "appID") - cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[3], "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA") - - //Assert the NonStandardPublisherMap hash table was built correctly - var found bool - for i := 0; i < len(cfg.GDPR.NonStandardPublishers); i++ { - _, found = cfg.GDPR.NonStandardPublisherMap[cfg.GDPR.NonStandardPublishers[i]] - cmpBools(t, "cfg.GDPR.NonStandardPublisherMap", found, true) - } - _, found = cfg.GDPR.NonStandardPublisherMap["appnexus"] - cmpBools(t, "cfg.GDPR.NonStandardPublisherMap", found, false) + assert.Equal(t, []string{"pub1", "pub2"}, cfg.GDPR.NonStandardPublishers, "gdpr.non_standard_publishers") + assert.Equal(t, map[string]struct{}{"pub1": {}, "pub2": {}}, cfg.GDPR.NonStandardPublisherMap, "gdpr.non_standard_publishers Hash Map") + + // Assert EEA Countries was correctly unmarshalled and the EEACountriesMap built correctly. + assert.Equal(t, []string{"eea1", "eea2"}, cfg.GDPR.EEACountries, "gdpr.eea_countries") + assert.Equal(t, map[string]struct{}{"eea1": {}, "eea2": {}}, cfg.GDPR.EEACountriesMap, "gdpr.eea_countries Hash Map") cmpBools(t, "ccpa.enforce", cfg.CCPA.Enforce, true) cmpBools(t, "lmt.enforce", cfg.LMT.Enforce, true) From fd59120452fc48a1d17d2e3e7730e9df73d7497d Mon Sep 17 00:00:00 2001 From: guscarreon Date: Tue, 21 Sep 2021 10:07:06 -0400 Subject: [PATCH 095/140] Stop storing requestDebugInfo boolean value in context (#1966) --- exchange/bidder.go | 20 ++-- exchange/bidder_test.go | 4 - exchange/exchange.go | 32 ++----- exchange/exchange_test.go | 36 -------- exchange/utils.go | 25 ++++- exchange/utils_test.go | 188 +++++++++++++++++++++++++++++++++++++- 6 files changed, 224 insertions(+), 81 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index 28be854c264..0bdaa648619 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -180,24 +180,20 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B // 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 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) + 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) } } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 1591d353bb0..1a630a962c7 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -69,7 +69,6 @@ func TestSingleBidder(t *testing.T) { } ctx := context.Background() - ctx = context.WithValue(ctx, DebugContextKey, true) for _, test := range testCases { mockBidderResponse := &adapters.BidderResponse{ @@ -170,7 +169,6 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { 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)) @@ -211,7 +209,6 @@ func TestSetGPCHeader(t *testing.T) { 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)) @@ -249,7 +246,6 @@ func TestSetGPCHeaderNil(t *testing.T) { 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)) diff --git a/exchange/exchange.go b/exchange/exchange.go index f6ec713fa28..8b4b49e74f2 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -32,10 +32,6 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" ) -type ContextKey string - -const DebugContextKey = ContextKey("debugInfo") - type extCacheInstructions struct { cacheBids, cacheVAST, returnCreative bool } @@ -188,18 +184,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * _, targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData() } - if debugLog == nil { - debugLog = &DebugLog{Enabled: false, DebugEnabledOrOverridden: false} - } - - requestDebugInfo := getDebugInfo(r.BidRequest, requestExt) - - debugInfo := debugLog.DebugEnabledOrOverridden || (requestDebugInfo && r.Account.DebugAllow) - debugLog.Enabled = debugLog.DebugEnabledOrOverridden || r.Account.DebugAllow - - if debugInfo { - ctx = e.makeDebugContext(ctx, debugInfo) - } + responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(r.BidRequest, requestExt, r.Account.DebugAllow, debugLog) bidAdjustmentFactors := getExtBidAdjustmentFactors(requestExt) @@ -224,7 +209,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * // Get currency rates conversions for the auction conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) - adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride) + adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride) var auc *auction var cacheErrs []error @@ -268,7 +253,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * errs = append(errs, dealErrs...) } - bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, errs) if debugLog.DebugEnabledOrOverridden { if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) @@ -286,9 +271,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * targData.setTargeting(auc, r.BidRequest.App != nil, bidCategory) } - bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, errs) } else { - bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) + bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, errs) if debugLog.DebugEnabledOrOverridden { @@ -301,7 +286,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } - if !r.Account.DebugAllow && requestDebugInfo && !debugLog.DebugOverride { + if !accountDebugAllow && !debugLog.DebugOverride { accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{ Code: errortypes.AccountLevelDebugDisabledWarningCode, Message: "debug turned off for account", @@ -411,11 +396,6 @@ func updateHbPbCatDur(bid *pbsOrtbBid, dealTier openrtb_ext.DealTier, bidCategor } } -func (e *exchange) makeDebugContext(ctx context.Context, debugInfo bool) (debugCtx context.Context) { - debugCtx = context.WithValue(ctx, DebugContextKey, debugInfo) - return -} - func (e *exchange) makeAuctionContext(ctx context.Context, needsCache bool) (auctionCtx context.Context, cancel context.CancelFunc) { auctionCtx = ctx cancel = func() {} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index ed378e7b021..6258d12154f 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -322,9 +322,6 @@ func TestDebugBehaviour(t *testing.T) { openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}), } - //request level debug key - ctx = context.WithValue(ctx, DebugContextKey, test.in.debug) - bidRequest.Test = test.in.test if test.in.debug { @@ -2025,38 +2022,6 @@ func TestTimeoutComputation(t *testing.T) { } } -func TestSetDebugContextKey(t *testing.T) { - // Test cases - testCases := []struct { - desc string - inDebugInfo bool - expectedDebugInfo bool - }{ - { - desc: "debugInfo flag on, we expect to find DebugContextKey key in context", - inDebugInfo: true, - expectedDebugInfo: true, - }, - { - desc: "debugInfo flag off, we don't expect to find DebugContextKey key in context", - inDebugInfo: false, - expectedDebugInfo: false, - }, - } - - // Setup test - ex := exchange{} - - // Run tests - for _, test := range testCases { - auctionCtx := ex.makeDebugContext(context.Background(), test.inDebugInfo) - - debugInfo := auctionCtx.Value(DebugContextKey) - assert.NotNil(t, debugInfo, "%s. Flag set, `debugInfo` shouldn't be nil") - assert.Equal(t, test.expectedDebugInfo, debugInfo.(bool), "Desc: %s. Incorrect value mapped to DebugContextKey(`debugInfo`) in the context\n", test.desc) - } -} - // TestExchangeJSON executes tests for all the *.json files in exchangetest. func TestExchangeJSON(t *testing.T) { if specFiles, err := ioutil.ReadDir("./exchangetest"); err == nil { @@ -2145,7 +2110,6 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { auctionRequest.StartTime = time.Unix(0, spec.StartTime*1e+6) } ctx := context.Background() - ctx = context.WithValue(ctx, DebugContextKey, true) bid, err := ex.HoldAuction(ctx, auctionRequest, debugLog) responseTimes := extractResponseTimes(t, filename, bid) diff --git a/exchange/utils.go b/exchange/utils.go index 1277cffd85f..59cc7f1e40a 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -690,7 +690,30 @@ func getExtTargetData(requestExt *openrtb_ext.ExtRequest, cacheInstructions *ext return targData } -func getDebugInfo(bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest) bool { +// getDebugInfo returns the boolean flags that allow for debug information in bidResponse.Ext, the SeatBid.httpcalls slice, and +// also sets the debugLog information +func getDebugInfo(bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest, accountDebugFlag bool, debugLog *DebugLog) (bool, bool, *DebugLog) { + requestDebugAllow := parseRequestDebugValues(bidRequest, requestExt) + debugLog = setDebugLogValues(accountDebugFlag, debugLog) + + responseDebugAllow := (requestDebugAllow && accountDebugFlag) || debugLog.DebugEnabledOrOverridden + accountDebugAllow := (requestDebugAllow && accountDebugFlag) || (debugLog.DebugEnabledOrOverridden && accountDebugFlag) + + return responseDebugAllow, accountDebugAllow, debugLog +} + +// setDebugLogValues initializes the DebugLog if nil. It also sets the value of the debugInfo flag +// used in HoldAuction +func setDebugLogValues(accountDebugFlag bool, debugLog *DebugLog) *DebugLog { + if debugLog == nil { + debugLog = &DebugLog{} + } + + debugLog.Enabled = debugLog.DebugEnabledOrOverridden || accountDebugFlag + return debugLog +} + +func parseRequestDebugValues(bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest) bool { return (bidRequest != nil && bidRequest.Test == 1) || (requestExt != nil && requestExt.Prebid.Debug) } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 6ce4b28ac33..14ebb8e9107 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1289,7 +1289,7 @@ func TestGetExtTargetData(t *testing.T) { } } -func TestGetDebugInfo(t *testing.T) { +func TestParseRequestDebugValues(t *testing.T) { type inTest struct { bidRequest *openrtb2.BidRequest requestExt *openrtb_ext.ExtRequest @@ -1336,12 +1336,84 @@ func TestGetDebugInfo(t *testing.T) { }, } for _, test := range testCases { - actualDebugInfo := getDebugInfo(test.in.bidRequest, test.in.requestExt) + actualDebugInfo := parseRequestDebugValues(test.in.bidRequest, test.in.requestExt) assert.Equal(t, test.out, actualDebugInfo, "%s. Unexpected debug value. \n", test.desc) } } +func TestSetDebugLogValues(t *testing.T) { + + type aTest struct { + desc string + inAccountDebugFlag bool + inDebugLog *DebugLog + expectedDebugLog *DebugLog + } + + testGroups := []struct { + desc string + testCases []aTest + }{ + + { + "nil debug log", + []aTest{ + { + desc: "accountDebugFlag false, expect all false flags in resulting debugLog", + inAccountDebugFlag: false, + inDebugLog: nil, + expectedDebugLog: &DebugLog{}, + }, + { + desc: "accountDebugFlag true, expect debugLog.Enabled to be true", + inAccountDebugFlag: true, + inDebugLog: nil, + expectedDebugLog: &DebugLog{Enabled: true}, + }, + }, + }, + { + "non-nil debug log", + []aTest{ + { + desc: "both accountDebugFlag and DebugEnabledOrOverridden are false, expect debugLog.Enabled to be false", + inAccountDebugFlag: false, + inDebugLog: &DebugLog{}, + expectedDebugLog: &DebugLog{}, + }, + { + desc: "accountDebugFlag false but DebugEnabledOrOverridden is true, expect debugLog.Enabled to be true", + inAccountDebugFlag: false, + inDebugLog: &DebugLog{DebugEnabledOrOverridden: true}, + expectedDebugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, + }, + { + desc: "accountDebugFlag true but DebugEnabledOrOverridden is false, expect debugLog.Enabled to be true", + inAccountDebugFlag: true, + inDebugLog: &DebugLog{}, + expectedDebugLog: &DebugLog{Enabled: true}, + }, + { + desc: "Both accountDebugFlag and DebugEnabledOrOverridden are true, expect debugLog.Enabled to be true", + inAccountDebugFlag: true, + inDebugLog: &DebugLog{DebugEnabledOrOverridden: true}, + expectedDebugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, + }, + }, + }, + } + + for _, group := range testGroups { + for _, tc := range group.testCases { + // run + actualDebugLog := setDebugLogValues(tc.inAccountDebugFlag, tc.inDebugLog) + // assertions + assert.Equal(t, tc.expectedDebugLog, actualDebugLog, "%s. %s", group.desc, tc.desc) + } + } +} + func TestGetExtBidAdjustmentFactors(t *testing.T) { testCases := []struct { desc string @@ -2164,6 +2236,118 @@ func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { } } +func TestGetDebugInfo(t *testing.T) { + type testInput struct { + debugEnabledOrOverridden bool + accountDebugFlag bool + } + type testOut struct { + responseDebugAllow bool + accountDebugAllow bool + debugLog *DebugLog + } + type testCase struct { + in testInput + expected testOut + } + + testGroups := []struct { + desc string + bidReq *openrtb2.BidRequest + tests []testCase + }{ + { + "Bid request doesn't call for debug info", + &openrtb2.BidRequest{Test: 0}, + []testCase{ + { + testInput{debugEnabledOrOverridden: false, accountDebugFlag: false}, + testOut{ + responseDebugAllow: false, + accountDebugAllow: false, + debugLog: &DebugLog{Enabled: false}, + }, + }, + { + testInput{debugEnabledOrOverridden: false, accountDebugFlag: true}, + testOut{ + responseDebugAllow: false, + accountDebugAllow: false, + debugLog: &DebugLog{Enabled: true}, + }, + }, + { + testInput{debugEnabledOrOverridden: true, accountDebugFlag: false}, + testOut{ + responseDebugAllow: true, + accountDebugAllow: false, + debugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, + }, + }, + { + testInput{debugEnabledOrOverridden: true, accountDebugFlag: true}, + testOut{ + responseDebugAllow: true, + accountDebugAllow: true, + debugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, + }, + }, + }, + }, + { + "Bid request requires debug info", + &openrtb2.BidRequest{Test: 1}, + []testCase{ + { + testInput{debugEnabledOrOverridden: false, accountDebugFlag: false}, + testOut{ + responseDebugAllow: false, + accountDebugAllow: false, + debugLog: &DebugLog{Enabled: false}, + }, + }, + { + testInput{debugEnabledOrOverridden: false, accountDebugFlag: true}, + testOut{ + responseDebugAllow: true, + accountDebugAllow: true, + debugLog: &DebugLog{Enabled: true}, + }, + }, + { + testInput{debugEnabledOrOverridden: true, accountDebugFlag: false}, + testOut{ + responseDebugAllow: true, + accountDebugAllow: false, + debugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, + }, + }, + { + testInput{debugEnabledOrOverridden: true, accountDebugFlag: true}, + testOut{ + responseDebugAllow: true, + accountDebugAllow: true, + debugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, + }, + }, + }, + }, + } + for _, group := range testGroups { + for i, tc := range group.tests { + inDebugLog := &DebugLog{DebugEnabledOrOverridden: tc.in.debugEnabledOrOverridden} + + // run + responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(group.bidReq, nil, tc.in.accountDebugFlag, inDebugLog) + + // assertions + assert.Equal(t, tc.expected.responseDebugAllow, responseDebugAllow, "%s - %d", group.desc, i) + assert.Equal(t, tc.expected.accountDebugAllow, accountDebugAllow, "%s - %d", group.desc, i) + assert.Equal(t, tc.expected.debugLog, debugLog, "%s - %d", group.desc, i) + } + } +} + func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { testCases := []struct { description string From f11ba7748267a3283bf4fe685a06fb4280f183f4 Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Thu, 23 Sep 2021 20:19:33 +0300 Subject: [PATCH 096/140] Rubicon: Implement FPD resolving functionality (#1948) Co-authored-by: Serhii Nahornyi --- adapters/rubicon/rubicon.go | 386 ++++++++++--- adapters/rubicon/rubicon_test.go | 59 +- .../rubicontest/exemplary/app-imp-fpd.json | 411 +++++++++++++ .../rubicontest/exemplary/simple-video.json | 21 + .../rubicontest/exemplary/site-imp-fpd.json | 542 ++++++++++++++++++ .../rubicontest/exemplary/user-fpd.json | 344 +++++++++++ .../supplemental/no-site-content-data.json | 14 + .../supplemental/no-site-content.json | 14 + openrtb_ext/imp_rubicon.go | 1 + 9 files changed, 1704 insertions(+), 88 deletions(-) create mode 100644 adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json create mode 100644 adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json create mode 100644 adapters/rubicon/rubicontest/exemplary/user-fpd.json diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 1878c41feb0..80c62df16a1 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/buger/jsonparser" "io/ioutil" "net/http" "net/url" @@ -30,7 +31,6 @@ type RubiconAdapter struct { XAPIPassword string } -// used for cookies and such func (a *RubiconAdapter) Name() string { return "rubicon" } @@ -73,16 +73,17 @@ type rubiconImpExtRPTrack struct { MintVersion string `json:"mint_version"` } +type rubiconImpExt struct { + RP rubiconImpExtRP `json:"rp,omitempty"` + GPID string `json:"gpid,omitempty"` +} + type rubiconImpExtRP struct { ZoneID int `json:"zone_id"` Target json.RawMessage `json:"target,omitempty"` Track rubiconImpExtRPTrack `json:"track"` } -type rubiconImpExt struct { - RP rubiconImpExtRP `json:"rp"` -} - type rubiconUserExtRP struct { Target json.RawMessage `json:"target,omitempty"` } @@ -102,6 +103,7 @@ type rubiconUserExt struct { TpID []rubiconExtUserTpID `json:"tpid,omitempty"` RP rubiconUserExtRP `json:"rp"` LiverampIdl string `json:"liveramp_idl,omitempty"` + Data json.RawMessage `json:"data,omitempty"` } type rubiconSiteExtRP struct { @@ -131,18 +133,6 @@ type rubiconBannerExt struct { RP rubiconBannerExtRP `json:"rp"` } -type ExtImpContextData struct { - AdSlot string `json:"adslot,omitempty"` -} - -type ExtImpContext struct { - Data ExtImpContextData `json:"data,omitempty"` -} - -type ExtImpWithContext struct { - Context ExtImpContext `json:"context,omitempty"` // First Party Data context -} - // ***** Video Extension ***** type rubiconVideoParams struct { Language string `json:"language,omitempty"` @@ -701,11 +691,10 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada requestImpCopy := request.Imp rubiconRequest := *request - for i := 0; i < numRequests; i++ { - thisImp := requestImpCopy[i] + for _, imp := range requestImpCopy { var bidderExt adapters.ExtImpBidder - if err = json.Unmarshal(thisImp.Ext, &bidderExt); err != nil { + if err = json.Unmarshal(imp.Ext, &bidderExt); err != nil { errs = append(errs, &errortypes.BadInput{ Message: err.Error(), }) @@ -720,36 +709,15 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } - target := rubiconExt.Inventory - if rubiconExt.Inventory != nil { - rubiconExtInventory := make(map[string]interface{}) - if err := json.Unmarshal(rubiconExt.Inventory, &rubiconExtInventory); err != nil { - errs = append(errs, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - - var extImpWithContext ExtImpWithContext - if err := json.Unmarshal(thisImp.Ext, &extImpWithContext); err != nil { - errs = append(errs, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - - // Copy imp[].ext.context.data.adslot is copied to imp[].ext.rp.target.dfp_ad_unit_code, - // but with any leading slash dropped - adSlot := extImpWithContext.Context.Data.AdSlot - if adSlot != "" { - rubiconExtInventory["dfp_ad_unit_code"] = strings.TrimLeft(adSlot, "/") - - target, err = json.Marshal(&rubiconExtInventory) - if err != nil { - errs = append(errs, err) - continue - } - } + target, err := updateImpRpTargetWithFpdAttributes(rubiconExt, imp, request.Site, request.App) + if err != nil { + errs = append(errs, err) + continue + } + adSlot, err := getAdSlot(imp) + if err != nil { + errs = append(errs, err) + continue } impExt := rubiconImpExt{ @@ -758,35 +726,36 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada Target: target, Track: rubiconImpExtRPTrack{Mint: "", MintVersion: ""}, }, + GPID: adSlot, } - thisImp.Ext, err = json.Marshal(&impExt) + imp.Ext, err = json.Marshal(&impExt) if err != nil { errs = append(errs, err) continue } - resolvedBidFloor, err := resolveBidFloor(thisImp.BidFloor, thisImp.BidFloorCur, reqInfo) + resolvedBidFloor, err := resolveBidFloor(imp.BidFloor, imp.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), + imp.BidFloorCur), }) continue } if resolvedBidFloor > 0 { - thisImp.BidFloorCur = "USD" - thisImp.BidFloor = resolvedBidFloor + imp.BidFloorCur = "USD" + imp.BidFloor = resolvedBidFloor } if request.User != nil { userCopy := *request.User - - target, err := updateExtWithIabAttribute(rubiconExt.Visitor, userCopy.Data, []int{4}) + target, err := updateUserRpTargetWithFpdAttributes(rubiconExt.Visitor, userCopy) if err != nil { errs = append(errs, err) continue } + userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: target}} if request.User.Ext != nil { @@ -822,6 +791,10 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada errs = append(errs, err) continue } + userCopy.Geo = nil + userCopy.Yob = 0 + userCopy.Gender = "" + rubiconRequest.User = &userCopy } @@ -832,13 +805,13 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada rubiconRequest.Device = &deviceCopy } - isVideo := isVideo(thisImp) + isVideo := isVideo(imp) if isVideo { - videoCopy := *thisImp.Video + videoCopy := *imp.Video videoSizeId := rubiconExt.Video.VideoSizeID if videoSizeId == 0 { - resolvedSizeId, err := resolveVideoSizeId(thisImp.Video.Placement, thisImp.Instl, thisImp.ID) + resolvedSizeId, err := resolveVideoSizeId(imp.Video.Placement, imp.Instl, imp.ID) if err != nil { errs = append(errs, err) continue @@ -853,23 +826,23 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada } videoExt := rubiconVideoExt{Skip: rubiconExt.Video.Skip, SkipDelay: rubiconExt.Video.SkipDelay, VideoType: videoType, RP: rubiconVideoExtRP{SizeID: videoSizeId}} videoCopy.Ext, err = json.Marshal(&videoExt) - thisImp.Video = &videoCopy - thisImp.Banner = nil + imp.Video = &videoCopy + imp.Banner = nil } else { - primarySizeID, altSizeIDs, err := parseRubiconSizes(thisImp.Banner.Format) + primarySizeID, altSizeIDs, err := parseRubiconSizes(imp.Banner.Format) if err != nil { errs = append(errs, err) continue } bannerExt := rubiconBannerExt{RP: rubiconBannerExtRP{SizeID: primarySizeID, AltSizeIDs: altSizeIDs, MIME: "text/html"}} - bannerCopy := *thisImp.Banner + bannerCopy := *imp.Banner bannerCopy.Ext, err = json.Marshal(&bannerExt) if err != nil { errs = append(errs, err) continue } - thisImp.Banner = &bannerCopy - thisImp.Video = nil + imp.Banner = &bannerCopy + imp.Video = nil } pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: rubiconExt.AccountId}} @@ -878,12 +851,16 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada siteCopy := *request.Site 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 + siteTarget := make(map[string]interface{}) + updateExtWithIabAttribute(siteTarget, siteCopy.Content.Data, []int{1, 2, 5, 6}) + if len(siteTarget) > 0 { + updatedSiteTarget, err := json.Marshal(siteTarget) + if err != nil { + errs = append(errs, err) + continue + } + siteExtRP.RP.Target = updatedSiteTarget } - siteExtRP.RP.Target = target } siteCopy.Ext, err = json.Marshal(&siteExtRP) @@ -910,7 +887,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada } } - rubiconRequest.Imp = []openrtb2.Imp{thisImp} + rubiconRequest.Imp = []openrtb2.Imp{imp} rubiconRequest.Cur = nil rubiconRequest.Ext = nil @@ -941,27 +918,272 @@ func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.Ext return bidFloor, nil } -func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) { +func updateImpRpTargetWithFpdAttributes(extImp openrtb_ext.ExtImpRubicon, imp openrtb2.Imp, + site *openrtb2.Site, app *openrtb2.App) (json.RawMessage, error) { + existingTarget, _, _, err := jsonparser.Get(imp.Ext, "rp", "target") + if isNotKeyPathError(err) { + return nil, err + } + target, err := rawJSONToMap(existingTarget) + if err != nil { + return nil, err + } + err = populateFirstPartyDataAttributes(extImp.Inventory, target) + if err != nil { + return nil, err + } + + if site != nil { + siteExtData, _, _, err := jsonparser.Get(site.Ext, "data") + if isNotKeyPathError(err) { + return nil, err + } + err = populateFirstPartyDataAttributes(siteExtData, target) + if err != nil { + return nil, err + } + if len(site.SectionCat) > 0 { + addStringArrayAttribute(site.SectionCat, target, "sectioncat") + } + if len(site.PageCat) > 0 { + addStringArrayAttribute(site.PageCat, target, "pagecat") + } + if site.Page != "" { + addStringAttribute(site.Page, target, "page") + } + if site.Ref != "" { + addStringAttribute(site.Ref, target, "ref") + } + if site.Search != "" { + addStringAttribute(site.Search, target, "search") + } + } else { + appExtData, _, _, err := jsonparser.Get(app.Ext, "data") + if isNotKeyPathError(err) { + return nil, err + } + err = populateFirstPartyDataAttributes(appExtData, target) + if err != nil { + return nil, err + } + if len(app.SectionCat) > 0 { + addStringArrayAttribute(app.SectionCat, target, "sectioncat") + } + if len(app.PageCat) > 0 { + addStringArrayAttribute(app.PageCat, target, "pagecat") + } + } + + impExtContextAttributes, _, _, err := jsonparser.Get(imp.Ext, "context", "data") + if isNotKeyPathError(err) { + return nil, err + } + + if len(impExtContextAttributes) > 0 { + err = populateFirstPartyDataAttributes(impExtContextAttributes, target) + if err != nil { + return nil, err + } + } else if impExtDataAttributes, _, _, err := jsonparser.Get(imp.Ext, "data"); err == nil && len(impExtDataAttributes) > 0 { + err = populateFirstPartyDataAttributes(impExtDataAttributes, target) + if err != nil { + return nil, err + } + } + if isNotKeyPathError(err) { + return nil, err + } + + if len(extImp.Keywords) > 0 { + addStringArrayAttribute(extImp.Keywords, target, "keywords") + } + updatedTarget, err := json.Marshal(target) + if err != nil { + return nil, err + } + return updatedTarget, nil +} + +func isNotKeyPathError(err error) bool { + return err != nil && err != jsonparser.KeyPathNotFoundError +} + +func addStringAttribute(attribute string, target map[string]interface{}, attributeName string) { + target[attributeName] = [1]string{attribute} +} + +func addStringArrayAttribute(attribute []string, target map[string]interface{}, attributeName string) { + target[attributeName] = attribute +} + +func getAdSlot(imp openrtb2.Imp) (string, error) { + var adSlot string + var parsingError error + jsonparser.EachKey(imp.Ext, func(idx int, value []byte, vt jsonparser.ValueType, err error) { + switch idx { + case 0: + adServerContextName, err := jsonparser.GetString(value, "name") + if isNotKeyPathError(err) { + parsingError = err + return + } + if adServerContextName == "gam" { + contextAdSlot, err := jsonparser.GetString(value, "adslot") + if isNotKeyPathError(err) { + parsingError = err + return + } + adSlot = contextAdSlot + } + } + }, []string{"context", "data", "adserver"}) + + if parsingError != nil { + return "", parsingError + } + + if adSlot != "" { + return adSlot, nil + } + + jsonparser.EachKey(imp.Ext, func(idx int, value []byte, vt jsonparser.ValueType, err error) { + switch idx { + case 0: + adServerDataName, err := jsonparser.GetString(value, "name") + if isNotKeyPathError(err) { + parsingError = err + return + } + if adServerDataName == "gam" { + dataAdSlot, err := jsonparser.GetString(value, "adslot") + if isNotKeyPathError(err) { + parsingError = err + return + } + adSlot = dataAdSlot + } + } + }, []string{"data", "adserver"}) + + if parsingError != nil { + return "", parsingError + } + + return adSlot, nil +} + +func updateUserRpTargetWithFpdAttributes(visitor json.RawMessage, user openrtb2.User) (json.RawMessage, error) { + existingTarget, _, _, err := jsonparser.Get(user.Ext, "rp", "target") + if isNotKeyPathError(err) { + return nil, err + } + target, err := rawJSONToMap(existingTarget) + if err != nil { + return nil, err + } + err = populateFirstPartyDataAttributes(visitor, target) + if err != nil { + return nil, err + } + userExtData, _, _, err := jsonparser.Get(user.Ext, "data") + if isNotKeyPathError(err) { + return nil, err + } + err = populateFirstPartyDataAttributes(userExtData, target) + if err != nil { + return nil, err + } + updateExtWithIabAttribute(target, user.Data, []int{4}) + + updatedTarget, err := json.Marshal(target) + if err != nil { + return nil, err + } + return updatedTarget, nil +} + +func updateExtWithIabAttribute(target map[string]interface{}, data []openrtb2.Data, segTaxes []int) { var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes) if len(segmentIdsToCopy) == 0 { - return target, nil + return + } + + target["iab"] = segmentIdsToCopy +} + +func populateFirstPartyDataAttributes(source json.RawMessage, target map[string]interface{}) error { + sourceAsMap, err := rawJSONToMap(source) + if err != nil { + return err + } + + for key, val := range sourceAsMap { + switch typedValue := val.(type) { + case string: + target[key] = [1]string{typedValue} + case float64: + if typedValue == float64(int(typedValue)) { + target[key] = [1]string{strconv.Itoa(int(typedValue))} + } + case bool: + target[key] = [1]string{strconv.FormatBool(typedValue)} + case []interface{}: + if isStringArray(typedValue) { + target[key] = typedValue + } + if isBoolArray(typedValue) { + target[key] = convertToStringArray(typedValue) + } + } + } + return nil +} + +func isStringArray(array []interface{}) bool { + for _, val := range array { + if _, ok := val.(string); !ok { + return false + } + } + + return true +} + +func isBoolArray(array []interface{}) bool { + for _, val := range array { + if _, ok := val.(bool); !ok { + return false + } } - extRPTarget := make(map[string]interface{}) + return true +} - if target != nil { - if err := json.Unmarshal(target, &extRPTarget); err != nil { - return nil, &errortypes.BadInput{Message: err.Error()} +func convertToStringArray(arr []interface{}) []string { + var stringArray []string + for _, val := range arr { + if boolVal, ok := val.(bool); ok { + stringArray = append(stringArray, strconv.FormatBool(boolVal)) } } - extRPTarget["iab"] = segmentIdsToCopy + return stringArray +} + +func rawJSONToMap(message json.RawMessage) (map[string]interface{}, error) { + if message == nil { + return make(map[string]interface{}), nil + } - jsonTarget, err := json.Marshal(&extRPTarget) + return mapFromRawJSON(message) +} +func mapFromRawJSON(message json.RawMessage) (map[string]interface{}, error) { + targetAsMap := make(map[string]interface{}) + err := json.Unmarshal(message, &targetAsMap) if err != nil { - return nil, &errortypes.BadInput{Message: err.Error()} + return nil, err } - return jsonTarget, nil + return targetAsMap, nil } func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string { diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 2d4c962b53f..9bfa04fa78f 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -35,6 +35,12 @@ type rubiAppendTrackerUrlTestScenario struct { expected string } +type rubiPopulateFpdAttributesScenario struct { + source json.RawMessage + target map[string]interface{} + result map[string]interface{} +} + type rubiSetNetworkIdTestScenario struct { bidExt *openrtb_ext.ExtBidPrebid buyer string @@ -1277,7 +1283,10 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { }, "context": { "data": { - "adslot": "///test-adslot" + "adserver": { + "adslot": "/test-adslot", + "name": "gam" + } } } }`), @@ -1302,14 +1311,52 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { if err := json.Unmarshal(rubiconReq.Imp[0].Ext, &rpImpExt); err != nil { t.Fatal("Error unmarshalling imp.ext") } + assert.Equal(t, rpImpExt.GPID, "/test-adslot") +} - rubiconExtInventory := make(map[string]interface{}) - if err := json.Unmarshal(rpImpExt.RP.Target, &rubiconExtInventory); err != nil { - t.Fatal("Error unmarshalling imp.ext.rp.target") +func TestOpenRTBFirstPartyDataPopulating(t *testing.T) { + testScenarios := []rubiPopulateFpdAttributesScenario{ + { + source: json.RawMessage(`{"sourceKey": ["sourceValue", "sourceValue2"]}`), + target: map[string]interface{}{"targetKey": []interface{}{"targetValue"}}, + result: map[string]interface{}{"targetKey": []interface{}{"targetValue"}, "sourceKey": []interface{}{"sourceValue", "sourceValue2"}}, + }, + { + source: json.RawMessage(`{"sourceKey": ["sourceValue", "sourceValue2"]}`), + target: make(map[string]interface{}), + result: map[string]interface{}{"sourceKey": []interface{}{"sourceValue", "sourceValue2"}}, + }, + { + source: json.RawMessage(`{"sourceKey": "sourceValue"}`), + target: make(map[string]interface{}), + result: map[string]interface{}{"sourceKey": [1]string{"sourceValue"}}, + }, + { + source: json.RawMessage(`{"sourceKey": true, "sourceKey2": [true, false, true]}`), + target: make(map[string]interface{}), + result: map[string]interface{}{"sourceKey": [1]string{"true"}, "sourceKey2": []string{"true", "false", "true"}}, + }, + { + source: json.RawMessage(`{"sourceKey": 1, "sourceKey2": [1, 2, 3]}`), + target: make(map[string]interface{}), + result: map[string]interface{}{"sourceKey": [1]string{"1"}}, + }, + { + source: json.RawMessage(`{"sourceKey": 1, "sourceKey2": 3.23}`), + target: make(map[string]interface{}), + result: map[string]interface{}{"sourceKey": [1]string{"1"}}, + }, + { + source: json.RawMessage(`{"sourceKey": {}}`), + target: make(map[string]interface{}), + result: make(map[string]interface{}), + }, } - assert.Equal(t, "test-adslot", rubiconExtInventory["dfp_ad_unit_code"], - "Unexpected dfp_ad_unit_code: %s", rubiconExtInventory["dfp_ad_unit_code"]) + for _, scenario := range testScenarios { + populateFirstPartyDataAttributes(scenario.source, scenario.target) + assert.Equal(t, scenario.result, scenario.target) + } } func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { diff --git a/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json b/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json new file mode 100644 index 00000000000..eec396f8873 --- /dev/null +++ b/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json @@ -0,0 +1,411 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "pagecat": [ + "val1", + "val2" + ], + "sectioncat": [ + "sectionCat1", + "sectionCat2" + ], + "ext": { + "data": { + "attr1": "val1", + "attr2": [ + "val21", + "val22" + ], + "attr3": true, + "attr4": [ + true, + false, + true, + true + ], + "attr5": 3, + "attr6": [ + 1, + 2, + 3 + ], + "attr7": 1.23 + } + } + }, + "user": { + "yob": 2000, + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "gender": "f", + "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": { + "data": { + "attr1": "val1", + "attr2": [ + "val21", + "val22" + ], + "attr3": true, + "attr4": [ + true, + false, + true, + true + ], + "attr5": 3, + "attr6": [ + 1, + 2, + 3 + ], + "attr7": 1.23 + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "context": { + "data": { + "adserver": { + "name": "gam", + "adslot": "someAdSlot" + }, + "dataAttr1": "dataVal1", + "dataAttr2": "dataVal2" + } + }, + "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" + }, + "app": { + "pagecat": [ + "val1", + "val2" + ], + "sectioncat": [ + "sectionCat1", + "sectionCat2" + ], + "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": { + "attr1": [ + "val1" + ], + "attr2": [ + "val21", + "val22" + ], + "attr3": [ + "true" + ], + "attr4": [ + "true", + "false", + "true", + "true" + ], + "attr5": [ + "3" + ], + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, + "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": { + "gpid": "someAdSlot", + "rp": { + "target": { + "dataAttr1": [ + "dataVal1" + ], + "dataAttr2": [ + "dataVal2" + ], + "attr1": [ + "val1" + ], + "attr2": [ + "val21", + "val22" + ], + "attr3": [ + "true" + ], + "attr4": [ + "true", + "false", + "true", + "true" + ], + "attr5": [ + "3" + ], + "pagecat": [ + "val1", + "val2" + ], + "sectioncat": [ + "sectionCat1", + "sectionCat2" + ] + }, + "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/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 7e5274f2c9b..592f9fb279f 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -10,6 +10,10 @@ "bundle": "com.wls.testwlsapplication" }, "site": { + "pagecat": [ + "val1", + "val2" + ], "content": { "data": [ { @@ -49,6 +53,13 @@ } }, "user": { + "yob": 2000, + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "gender": "f", "data": [ { "ext": { @@ -148,6 +159,10 @@ "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" }, "site": { + "pagecat": [ + "val1", + "val2" + ], "content": { "data": [ { @@ -298,6 +313,12 @@ }, "ext": { "rp": { + "target": { + "pagecat": [ + "val1", + "val2" + ] + }, "track": { "mint": "", "mint_version": "" diff --git a/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json b/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json new file mode 100644 index 00000000000..56f5ce6128e --- /dev/null +++ b/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json @@ -0,0 +1,542 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "pagecat": [ + "val1", + "val2" + ], + "sectioncat": [ + "sectionCat1", + "sectionCat2" + ], + "page": "somePage", + "ref": "someRef", + "search": "someSearch", + "ext": { + "data": { + "attr1": "val1", + "attr2": [ + "val21", + "val22" + ], + "attr3": true, + "attr4": [ + true, + false, + true, + true + ], + "attr5": 3, + "attr6": [ + 1, + 2, + 3 + ], + "attr7": 1.23 + } + }, + "content": { + "data": [ + { + "ext": { + "segtax": 1 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "2" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 2 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": 5 + }, + "segment": [ + { + "id": "idToCopy3" + } + ] + }, + { + "ext": { + "segtax": 6 + }, + "segment": [ + { + "id": "idToCopy4" + } + ] + }, + { + "ext": { + "segtax": [ + 2 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ] + } + }, + "user": { + "yob": 2000, + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "gender": "f", + "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": { + "data": { + "adserver": { + "name": "gam", + "adslot": "someAdSlot" + }, + "dataAttr1": "dataVal1", + "dataAttr2": "dataVal2" + }, + "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": { + "pagecat": [ + "val1", + "val2" + ], + "sectioncat": [ + "sectionCat1", + "sectionCat2" + ], + "page": "somePage", + "ref": "someRef", + "search": "someSearch", + "content": { + "data": [ + { + "ext": { + "segtax": 1 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "2" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 2 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": 5 + }, + "segment": [ + { + "id": "idToCopy3" + } + ] + }, + { + "ext": { + "segtax": 6 + }, + "segment": [ + { + "id": "idToCopy4" + } + ] + }, + { + "ext": { + "segtax": [ + 2 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ] + }, + "ext": { + "rp": { + "site_id": 113932, + "target": { + "iab": [ + "idToCopy", + "idToCopy2", + "idToCopy3", + "idToCopy4" + ] + } + } + }, + "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" + ] + } + } + } + }, + "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": { + "gpid": "someAdSlot", + "rp": { + "target": { + "dataAttr1": [ + "dataVal1" + ], + "dataAttr2": [ + "dataVal2" + ], + "attr1": [ + "val1" + ], + "attr2": [ + "val21", + "val22" + ], + "attr3": [ + "true" + ], + "attr4": [ + "true", + "false", + "true", + "true" + ], + "attr5": [ + "3" + ], + "page": [ + "somePage" + ], + "ref": [ + "someRef" + ], + "search": [ + "someSearch" + ], + "pagecat": [ + "val1", + "val2" + ], + "sectioncat": [ + "sectionCat1", + "sectionCat2" + ] + }, + "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/exemplary/user-fpd.json b/adapters/rubicon/rubicontest/exemplary/user-fpd.json new file mode 100644 index 00000000000..b2be736fefa --- /dev/null +++ b/adapters/rubicon/rubicontest/exemplary/user-fpd.json @@ -0,0 +1,344 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "site": { + "page": "somePage", + "ref": "someRef", + "search": "someSearch" + }, + "user": { + "yob": 2000, + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "gender": "f", + "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" + } + ] + } + ], + "keywords": "someKeywords", + "ext": { + "rp": { + "target": { + "someKey": "someValue" + } + }, + "data": { + "dataKey1": "dataValue1", + "dataKey2": [ + "dataValue2", + "dataValue3" + ], + "dataKey3": true + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "data": { + "adserver": { + "name": "gam", + "adslot": "someAdSlot" + }, + "dataAttr1": "dataVal1", + "dataAttr2": "dataVal2" + }, + "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": { + "page": "somePage", + "ref": "someRef", + "search": "someSearch", + "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": { + "someKey": "someValue", + "dataKey1": [ + "dataValue1" + ], + "dataKey2": [ + "dataValue2", + "dataValue3" + ], + "dataKey3": [ + "true" + ], + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + }, + "keywords": "someKeywords" + }, + "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": { + "gpid": "someAdSlot", + "rp": { + "target": { + "dataAttr1": [ + "dataVal1" + ], + "dataAttr2": [ + "dataVal2" + ], + "page": [ + "somePage" + ], + "ref": [ + "someRef" + ], + "search": [ + "someSearch" + ] + }, + "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-data.json b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json index 0be214da4bc..a1e1381591e 100644 --- a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json @@ -10,6 +10,10 @@ "bundle": "com.wls.testwlsapplication" }, "site": { + "pagecat": [ + "val1", + "val2" + ], "content": { } }, @@ -113,6 +117,10 @@ "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" }, "site": { + "pagecat": [ + "val1", + "val2" + ], "content": { }, "ext": { @@ -221,6 +229,12 @@ }, "ext": { "rp": { + "target": { + "pagecat": [ + "val1", + "val2" + ] + }, "track": { "mint": "", "mint_version": "" diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content.json b/adapters/rubicon/rubicontest/supplemental/no-site-content.json index 2e830a2dd00..6630799b786 100644 --- a/adapters/rubicon/rubicontest/supplemental/no-site-content.json +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content.json @@ -10,6 +10,10 @@ "bundle": "com.wls.testwlsapplication" }, "site": { + "pagecat": [ + "val1", + "val2" + ] }, "user": { "data": [ @@ -111,6 +115,10 @@ "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" }, "site": { + "pagecat": [ + "val1", + "val2" + ], "ext": { "rp": { "site_id": 113932 @@ -217,6 +225,12 @@ }, "ext": { "rp": { + "target": { + "pagecat": [ + "val1", + "val2" + ] + }, "track": { "mint": "", "mint_version": "" diff --git a/openrtb_ext/imp_rubicon.go b/openrtb_ext/imp_rubicon.go index ee43d9659b8..f9b15ed3223 100644 --- a/openrtb_ext/imp_rubicon.go +++ b/openrtb_ext/imp_rubicon.go @@ -10,6 +10,7 @@ type ExtImpRubicon struct { SiteId int `json:"siteId"` ZoneId int `json:"zoneId"` Inventory json.RawMessage `json:"inventory,omitempty"` + Keywords []string `json:"keywords,omitempty"` Visitor json.RawMessage `json:"visitor,omitempty"` Video rubiconVideoParams `json:"video"` Debug impExtRubiconDebug `json:"debug,omitempty"` From 657232bcdbae4c39867edd648ab3581ac0b7fc95 Mon Sep 17 00:00:00 2001 From: oath-jac <45564796+oath-jac@users.noreply.github.com> Date: Thu, 23 Sep 2021 20:23:04 +0300 Subject: [PATCH 097/140] New Adapter: yahoossp (Rebrand yssp to yahoossp keeping the yssp alias) (#1994) Co-authored-by: oath-jac --- adapters/{yssp => yahoossp}/params_test.go | 16 ++++++++-------- .../{yssp/yssp.go => yahoossp/yahoossp.go} | 12 ++++++------ .../yahoossp_test.go} | 14 +++++++------- .../exemplary/simple-app-banner.json | 2 +- .../exemplary/simple-banner.json | 2 +- .../supplemental/empty-banner-format.json | 0 .../supplemental/invalid-banner-width.json | 0 .../supplemental/non-banner-bids-ignored.json | 2 +- .../supplemental/required-nobidder-info.json | 0 .../supplemental/server-error.json | 0 .../server-response-wrong-impid.json | 2 +- .../yssp/yssptest/params/race/banner.json | 4 ---- config/config.go | 1 + exchange/adapter_builders.go | 7 ++++--- openrtb_ext/bidders.go | 2 ++ openrtb_ext/imp_yahoossp.go | 7 +++++++ openrtb_ext/imp_yssp.go | 7 ------- static/bidder-info/yahoossp.yaml | 15 +++++++++++++++ static/bidder-params/yahoossp.json | 19 +++++++++++++++++++ 19 files changed, 73 insertions(+), 39 deletions(-) rename adapters/{yssp => yahoossp}/params_test.go (61%) rename adapters/{yssp/yssp.go => yahoossp/yahoossp.go} (93%) rename adapters/{yssp/yssp_test.go => yahoossp/yahoossp_test.go} (56%) rename adapters/{yssp/yssptest => yahoossp/yahoossptest}/exemplary/simple-app-banner.json (98%) rename adapters/{yssp/yssptest => yahoossp/yahoossptest}/exemplary/simple-banner.json (98%) rename adapters/{yssp/yssptest => yahoossp/yahoossptest}/supplemental/empty-banner-format.json (100%) rename adapters/{yssp/yssptest => yahoossp/yahoossptest}/supplemental/invalid-banner-width.json (100%) rename adapters/{yssp/yssptest => yahoossp/yahoossptest}/supplemental/non-banner-bids-ignored.json (98%) rename adapters/{yssp/yssptest => yahoossp/yahoossptest}/supplemental/required-nobidder-info.json (100%) rename adapters/{yssp/yssptest => yahoossp/yahoossptest}/supplemental/server-error.json (100%) rename adapters/{yssp/yssptest => yahoossp/yahoossptest}/supplemental/server-response-wrong-impid.json (98%) delete mode 100644 adapters/yssp/yssptest/params/race/banner.json create mode 100644 openrtb_ext/imp_yahoossp.go delete mode 100644 openrtb_ext/imp_yssp.go create mode 100644 static/bidder-info/yahoossp.yaml create mode 100644 static/bidder-params/yahoossp.json diff --git a/adapters/yssp/params_test.go b/adapters/yahoossp/params_test.go similarity index 61% rename from adapters/yssp/params_test.go rename to adapters/yahoossp/params_test.go index 66146377f0c..47d8d26894b 100644 --- a/adapters/yssp/params_test.go +++ b/adapters/yahoossp/params_test.go @@ -1,4 +1,4 @@ -package yssp +package yahoossp import ( "encoding/json" @@ -7,11 +7,11 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -// This file actually intends to test static/bidder-params/yssp.json +// This file actually intends to test static/bidder-params/yahoossp.json // -// These also validate the format of the external API: request.imp[i].ext.yssp +// These also validate the format of the external API: request.imp[i].ext.yahoossp -// TestValidParams makes sure that the yssp schema accepts all imp.ext fields which we intend to support. +// TestValidParams makes sure that the yahoossp 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 { @@ -19,13 +19,13 @@ func TestValidParams(t *testing.T) { } for _, validParam := range validParams { - if err := validator.Validate(openrtb_ext.BidderYSSP, json.RawMessage(validParam)); err != nil { - t.Errorf("Schema rejected yssp params: %s", validParam) + if err := validator.Validate(openrtb_ext.BidderYahooSSP, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected yahoossp params: %s", validParam) } } } -// TestInvalidParams makes sure that the yssp schema rejects all the imp.ext fields we don't support. +// TestInvalidParams makes sure that the yahoossp 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 { @@ -33,7 +33,7 @@ func TestInvalidParams(t *testing.T) { } for _, invalidParam := range invalidParams { - if err := validator.Validate(openrtb_ext.BidderYSSP, json.RawMessage(invalidParam)); err == nil { + if err := validator.Validate(openrtb_ext.BidderYahooSSP, json.RawMessage(invalidParam)); err == nil { t.Errorf("Schema allowed unexpected params: %s", invalidParam) } } diff --git a/adapters/yssp/yssp.go b/adapters/yahoossp/yahoossp.go similarity index 93% rename from adapters/yssp/yssp.go rename to adapters/yahoossp/yahoossp.go index a73398ca3c0..652d57f995c 100644 --- a/adapters/yssp/yssp.go +++ b/adapters/yahoossp/yahoossp.go @@ -1,4 +1,4 @@ -package yssp +package yahoossp import ( "encoding/json" @@ -41,8 +41,8 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E continue } - var ysspExt openrtb_ext.ExtImpYSSP - err = json.Unmarshal(bidderExt.Bidder, &ysspExt) + var yahoosspExt openrtb_ext.ExtImpYahooSSP + err = json.Unmarshal(bidderExt.Bidder, &yahoosspExt) if err != nil { err = &errortypes.BadInput{ Message: fmt.Sprintf("imp #%d: %s", idx, err.Error()), @@ -64,7 +64,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E reqCopy.App = &appCopy } - if err := changeRequestForBidService(&reqCopy, &ysspExt); err != nil { + if err := changeRequestForBidService(&reqCopy, &yahoosspExt); err != nil { errors = append(errors, err) continue } @@ -147,7 +147,7 @@ func getImpInfo(impId string, imps []openrtb2.Imp) (bool, openrtb_ext.BidType) { return exists, mediaType } -func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpYSSP) error { +func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpYahooSSP) error { /* Always override the tag ID and (site ID or app ID) of the request */ request.Imp[0].TagID = extension.Pos if request.Site != nil { @@ -180,7 +180,7 @@ func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb return nil } -// Builder builds a new instance of the YSSP adapter for the given bidder with the given config. +// Builder builds a new instance of the YahooSSP 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, diff --git a/adapters/yssp/yssp_test.go b/adapters/yahoossp/yahoossp_test.go similarity index 56% rename from adapters/yssp/yssp_test.go rename to adapters/yahoossp/yahoossp_test.go index ee99d67211a..214457c0b24 100644 --- a/adapters/yssp/yssp_test.go +++ b/adapters/yahoossp/yahoossp_test.go @@ -1,4 +1,4 @@ -package yssp +package yahoossp import ( "testing" @@ -9,8 +9,8 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -func TestYSSPBidderEndpointConfig(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderYSSP, config.Adapter{ +func TestYahooSSPBidderEndpointConfig(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderYahooSSP, config.Adapter{ Endpoint: "http://localhost/bid", }) @@ -18,17 +18,17 @@ func TestYSSPBidderEndpointConfig(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } - bidderYSSP := bidder.(*adapter) + bidderYahooSSP := bidder.(*adapter) - assert.Equal(t, "http://localhost/bid", bidderYSSP.URI) + assert.Equal(t, "http://localhost/bid", bidderYahooSSP.URI) } func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderYSSP, config.Adapter{}) + bidder, buildErr := Builder(openrtb_ext.BidderYahooSSP, config.Adapter{}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } - adapterstest.RunJSONBidderTest(t, "yssptest", bidder) + adapterstest.RunJSONBidderTest(t, "yahoossptest", bidder) } diff --git a/adapters/yssp/yssptest/exemplary/simple-app-banner.json b/adapters/yahoossp/yahoossptest/exemplary/simple-app-banner.json similarity index 98% rename from adapters/yssp/yssptest/exemplary/simple-app-banner.json rename to adapters/yahoossp/yahoossptest/exemplary/simple-app-banner.json index a28913f8b9d..319e22e31dd 100644 --- a/adapters/yssp/yssptest/exemplary/simple-app-banner.json +++ b/adapters/yahoossp/yahoossptest/exemplary/simple-app-banner.json @@ -77,7 +77,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yssp", + "seat": "yahoossp", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yssp/yssptest/exemplary/simple-banner.json b/adapters/yahoossp/yahoossptest/exemplary/simple-banner.json similarity index 98% rename from adapters/yssp/yssptest/exemplary/simple-banner.json rename to adapters/yahoossp/yahoossptest/exemplary/simple-banner.json index 4a71473b4b4..344bbe9af94 100644 --- a/adapters/yssp/yssptest/exemplary/simple-banner.json +++ b/adapters/yahoossp/yahoossptest/exemplary/simple-banner.json @@ -77,7 +77,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yssp", + "seat": "yahoossp", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yssp/yssptest/supplemental/empty-banner-format.json b/adapters/yahoossp/yahoossptest/supplemental/empty-banner-format.json similarity index 100% rename from adapters/yssp/yssptest/supplemental/empty-banner-format.json rename to adapters/yahoossp/yahoossptest/supplemental/empty-banner-format.json diff --git a/adapters/yssp/yssptest/supplemental/invalid-banner-width.json b/adapters/yahoossp/yahoossptest/supplemental/invalid-banner-width.json similarity index 100% rename from adapters/yssp/yssptest/supplemental/invalid-banner-width.json rename to adapters/yahoossp/yahoossptest/supplemental/invalid-banner-width.json diff --git a/adapters/yssp/yssptest/supplemental/non-banner-bids-ignored.json b/adapters/yahoossp/yahoossptest/supplemental/non-banner-bids-ignored.json similarity index 98% rename from adapters/yssp/yssptest/supplemental/non-banner-bids-ignored.json rename to adapters/yahoossp/yahoossptest/supplemental/non-banner-bids-ignored.json index 94772a4b039..6f40e2a93f6 100644 --- a/adapters/yssp/yssptest/supplemental/non-banner-bids-ignored.json +++ b/adapters/yahoossp/yahoossptest/supplemental/non-banner-bids-ignored.json @@ -74,7 +74,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yssp", + "seat": "yahoossp", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "test-imp-id", diff --git a/adapters/yssp/yssptest/supplemental/required-nobidder-info.json b/adapters/yahoossp/yahoossptest/supplemental/required-nobidder-info.json similarity index 100% rename from adapters/yssp/yssptest/supplemental/required-nobidder-info.json rename to adapters/yahoossp/yahoossptest/supplemental/required-nobidder-info.json diff --git a/adapters/yssp/yssptest/supplemental/server-error.json b/adapters/yahoossp/yahoossptest/supplemental/server-error.json similarity index 100% rename from adapters/yssp/yssptest/supplemental/server-error.json rename to adapters/yahoossp/yahoossptest/supplemental/server-error.json diff --git a/adapters/yssp/yssptest/supplemental/server-response-wrong-impid.json b/adapters/yahoossp/yahoossptest/supplemental/server-response-wrong-impid.json similarity index 98% rename from adapters/yssp/yssptest/supplemental/server-response-wrong-impid.json rename to adapters/yahoossp/yahoossptest/supplemental/server-response-wrong-impid.json index 1e893127df4..5b33f3af67d 100644 --- a/adapters/yssp/yssptest/supplemental/server-response-wrong-impid.json +++ b/adapters/yahoossp/yahoossptest/supplemental/server-response-wrong-impid.json @@ -76,7 +76,7 @@ "id": "test-request-id", "seatbid": [ { - "seat": "yssp", + "seat": "yahoossp", "bid": [{ "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", "impid": "wrong", diff --git a/adapters/yssp/yssptest/params/race/banner.json b/adapters/yssp/yssptest/params/race/banner.json deleted file mode 100644 index 739ec3c024b..00000000000 --- a/adapters/yssp/yssptest/params/race/banner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "dcn": "2345", - "pos": "header" -} diff --git a/config/config.go b/config/config.go index a8d85f1fe76..75203b9f89d 100644 --- a/config/config.go +++ b/config/config.go @@ -869,6 +869,7 @@ func SetupViper(v *viper.Viper, filename string) { 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:0.1.0") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804") + v.SetDefault("adapters.yahoossp.disabled", true) v.SetDefault("adapters.yeahmobi.endpoint", "https://{{.Host}}/prebid/bid") v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/") v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 630f18b6a3e..c796df06547 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -120,11 +120,11 @@ import ( "github.com/prebid/prebid-server/adapters/valueimpression" "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" + "github.com/prebid/prebid-server/adapters/yahoossp" "github.com/prebid/prebid-server/adapters/yeahmobi" "github.com/prebid/prebid-server/adapters/yieldlab" "github.com/prebid/prebid-server/adapters/yieldmo" "github.com/prebid/prebid-server/adapters/yieldone" - "github.com/prebid/prebid-server/adapters/yssp" "github.com/prebid/prebid-server/adapters/zeroclickfraud" "github.com/prebid/prebid-server/openrtb_ext" ) @@ -252,15 +252,16 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderUnicorn: unicorn.Builder, openrtb_ext.BidderUnruly: unruly.Builder, openrtb_ext.BidderValueImpression: valueimpression.Builder, - openrtb_ext.BidderVerizonMedia: yssp.Builder, + openrtb_ext.BidderVerizonMedia: yahoossp.Builder, openrtb_ext.BidderViewdeos: adtelligent.Builder, openrtb_ext.BidderVisx: visx.Builder, openrtb_ext.BidderVrtcal: vrtcal.Builder, + openrtb_ext.BidderYahooSSP: yahoossp.Builder, openrtb_ext.BidderYeahmobi: yeahmobi.Builder, openrtb_ext.BidderYieldlab: yieldlab.Builder, openrtb_ext.BidderYieldmo: yieldmo.Builder, openrtb_ext.BidderYieldone: yieldone.Builder, - openrtb_ext.BidderYSSP: yssp.Builder, + openrtb_ext.BidderYSSP: yahoossp.Builder, openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, } } diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 88f816c293c..69ed1f42413 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -195,6 +195,7 @@ const ( BidderVisx BidderName = "visx" BidderViewdeos BidderName = "viewdeos" BidderVrtcal BidderName = "vrtcal" + BidderYahooSSP BidderName = "yahoossp" BidderYeahmobi BidderName = "yeahmobi" BidderYieldlab BidderName = "yieldlab" BidderYieldmo BidderName = "yieldmo" @@ -328,6 +329,7 @@ func CoreBidderNames() []BidderName { BidderViewdeos, BidderVisx, BidderVrtcal, + BidderYahooSSP, BidderYeahmobi, BidderYieldlab, BidderYieldmo, diff --git a/openrtb_ext/imp_yahoossp.go b/openrtb_ext/imp_yahoossp.go new file mode 100644 index 00000000000..e3183c865a8 --- /dev/null +++ b/openrtb_ext/imp_yahoossp.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpYahooSSP defines the contract for bidrequest.imp[i].ext.yahoossp +type ExtImpYahooSSP struct { + Dcn string `json:"dcn"` + Pos string `json:"pos"` +} diff --git a/openrtb_ext/imp_yssp.go b/openrtb_ext/imp_yssp.go deleted file mode 100644 index 054836ec21d..00000000000 --- a/openrtb_ext/imp_yssp.go +++ /dev/null @@ -1,7 +0,0 @@ -package openrtb_ext - -// ExtImpYSSP defines the contract for bidrequest.imp[i].ext.yssp -type ExtImpYSSP struct { - Dcn string `json:"dcn"` - Pos string `json:"pos"` -} diff --git a/static/bidder-info/yahoossp.yaml b/static/bidder-info/yahoossp.yaml new file mode 100644 index 00000000000..06d0cb189e5 --- /dev/null +++ b/static/bidder-info/yahoossp.yaml @@ -0,0 +1,15 @@ +maintainer: + email: "hb-fe-tech@oath.com" +gvlVendorID: 25 +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner +userSync: + # yahoossp supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - redirect \ No newline at end of file diff --git a/static/bidder-params/yahoossp.json b/static/bidder-params/yahoossp.json new file mode 100644 index 00000000000..9d971149339 --- /dev/null +++ b/static/bidder-params/yahoossp.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "YahooSSP Adapter Params", + "description": "A schema which validates params accepted by the YahooSSP adapter", + "type": "object", + "properties": { + "dcn": { + "type": "string", + "minLength": 1, + "description": "Site ID provided by One Mobile" + }, + "pos": { + "type": "string", + "minLength": 1, + "description": "Placement ID" + } + }, + "required": ["dcn", "pos"] +} From 8b261d63fbf6abe7b20eca3e6725598717f44ae2 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Mon, 27 Sep 2021 11:35:45 -0700 Subject: [PATCH 098/140] Interstitial behavior fix for issue 1923 (#1957) * add imp and instl logic * change operator * update tests * update test banner object * add impIndex to error * fix error message * fix banner W & H * change error instl value to 0 * change impIndex to 0 * update impIndex * impIndex 0 * update with isInterstitial * update function isInterstitial * fix function * revert * get tests to pass before fix * pass function * add tests back * simplify function * refactor tests for new format * fix test function * add imp.index to test * fix impIndex variable * update test description * more context * add json tests --- endpoints/openrtb2/auction.go | 10 +++-- endpoints/openrtb2/auction_test.go | 32 ++++++++++++++++ .../invalid-whole/banner-no-size.json | 24 ++++++++++++ .../exemplary/interstitial-no-size.json | 38 +++++++++++++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/banner-no-size.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/interstitial-no-size.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 579d6687034..44b05f4057c 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -548,7 +548,7 @@ func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]stri return []error{fmt.Errorf("request.imp[%d] must contain at least one of \"banner\", \"video\", \"audio\", or \"native\"", index)} } - if err := validateBanner(imp.Banner, index); err != nil { + if err := validateBanner(imp.Banner, index, isInterstitial(imp)); err != nil { return []error{err} } @@ -576,7 +576,11 @@ func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]stri return nil } -func validateBanner(banner *openrtb2.Banner, impIndex int) error { +func isInterstitial(imp *openrtb2.Imp) bool { + return imp.Instl == 1 +} + +func validateBanner(banner *openrtb2.Banner, impIndex int, isInterstitial bool) error { if banner == nil { return nil } @@ -606,7 +610,7 @@ func validateBanner(banner *openrtb2.Banner, impIndex int) error { } hasRootSize := banner.H != nil && banner.W != nil && *banner.H > 0 && *banner.W > 0 - if !hasRootSize && len(banner.Format) == 0 { + if !hasRootSize && len(banner.Format) == 0 && !isInterstitial { return fmt.Errorf("request.imp[%d].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements.", impIndex) } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index ebf1899436e..5f4a2902b86 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -3715,3 +3715,35 @@ type fakeUUIDGenerator struct { func (f fakeUUIDGenerator) Generate() (string, error) { return f.id, f.err } + +func TestValidateBanner(t *testing.T) { + impIndex := 0 + + testCases := []struct { + description string + banner *openrtb2.Banner + impIndex int + isInterstitial bool + expectedError error + }{ + { + description: "isInterstitial Equals False (not set to 1)", + banner: &openrtb2.Banner{W: nil, H: nil, Format: nil}, + impIndex: impIndex, + isInterstitial: false, + expectedError: errors.New("request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements."), + }, + { + description: "isInterstitial Equals True (is set to 1)", + banner: &openrtb2.Banner{W: nil, H: nil, Format: nil}, + impIndex: impIndex, + isInterstitial: true, + expectedError: nil, + }, + } + + for _, test := range testCases { + result := validateBanner(test.banner, test.impIndex, test.isInterstitial) + assert.Equal(t, test.expectedError, result, test.description) + } +} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-no-size.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-no-size.json new file mode 100644 index 00000000000..555fba7093f --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-no-size.json @@ -0,0 +1,24 @@ +{ + "description": "Banner Ad Without Sizes Defined", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": {}, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + }] + }, + "expectedErrorMessage": "Invalid request: request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements.", + "expectedReturnCode": 400 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/interstitial-no-size.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/interstitial-no-size.json new file mode 100644 index 00000000000..6100769a9d0 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/interstitial-no-size.json @@ -0,0 +1,38 @@ +{ + "description": "Interstitial Banner Ad Without Sizes Defined", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "instl": 1, + "banner": {}, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + } + } + }] + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [{ + "bid": [{ + "id": "appnexus-bid", + "impid": "", + "price": 0 + }], + "seat": "appnexus-bids" + }], + "bidid": "test bid id", + "cur": "USD", + "nbr": 0 + }, + "expectedReturnCode": 200 +} From 310dbad3ba00b33d0daf5e32c8c52380a9282152 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 27 Sep 2021 16:00:57 -0400 Subject: [PATCH 099/140] Remove Dummy Terminology From Core Code (#2007) * DummyMetricsEngine -> NilMetricsEngine * Legacy AccountsService.Set() Is Completely Unused * Remove Dummy Reference From Benchmark Tests * Remove Dummy Reference From Test * Misc Test References --- cache/dummycache/dummycache.go | 5 -- cache/filecache/filecache.go | 5 -- cache/legacy.go | 1 - cache/postgrescache/postgrescache.go | 5 -- endpoints/auction_test.go | 8 +-- endpoints/openrtb2/amp_auction_test.go | 24 ++++---- endpoints/openrtb2/auction_benchmark_test.go | 14 ++--- endpoints/openrtb2/auction_test.go | 48 +++++++-------- endpoints/openrtb2/video_auction_test.go | 6 +- endpoints/setuid_test.go | 4 +- exchange/adapter_util_test.go | 2 +- exchange/bidder_test.go | 28 ++++----- exchange/exchange_test.go | 56 ++++++++--------- exchange/targeting_test.go | 4 +- exchange/utils_test.go | 41 +++++++++---- metrics/config/metrics.go | 63 ++++++++++---------- metrics/config/metrics_test.go | 8 +-- prebid_cache_client/client_test.go | 2 +- stored_requests/events/events_test.go | 8 +-- 19 files changed, 167 insertions(+), 165 deletions(-) diff --git a/cache/dummycache/dummycache.go b/cache/dummycache/dummycache.go index 8cf9f2c4dff..02fe726d043 100644 --- a/cache/dummycache/dummycache.go +++ b/cache/dummycache/dummycache.go @@ -38,11 +38,6 @@ func (s *accountService) Get(id string) (*cache.Account, error) { }, nil } -// Set will always return nil since this is a dummy service -func (s *accountService) Set(account *cache.Account) error { - return nil -} - // ConfigService not supported, always returns an error type configService struct { c string diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go index 0c1ba435388..7bc4bea43f0 100644 --- a/cache/filecache/filecache.go +++ b/cache/filecache/filecache.go @@ -103,11 +103,6 @@ func (s *accountService) Get(id string) (*cache.Account, error) { }, nil } -// Set will always return nil since this is a dummy service -func (s *accountService) Set(account *cache.Account) error { - return nil -} - // ConfigService not supported, always returns an error type configService struct { shared *shared diff --git a/cache/legacy.go b/cache/legacy.go index 427ec74d9d8..19c5ae5a4fe 100644 --- a/cache/legacy.go +++ b/cache/legacy.go @@ -25,7 +25,6 @@ type Cache interface { type AccountsService interface { Get(string) (*Account, error) - Set(*Account) error } type ConfigService interface { diff --git a/cache/postgrescache/postgrescache.go b/cache/postgrescache/postgrescache.go index df8b8fe49b2..2333e08269e 100644 --- a/cache/postgrescache/postgrescache.go +++ b/cache/postgrescache/postgrescache.go @@ -104,11 +104,6 @@ func decodeAccount(b []byte) *cache.Account { return &account } -// Set the account in postgres and the lru cache -func (s *accountService) Set(account *cache.Account) error { - return nil -} - // ConfigService type configService struct { shared *shared diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index 522b38babcf..1a30e025faa 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -357,7 +357,7 @@ func TestCacheVideoOnly(t *testing.T) { }, nil, nil) prebid_cache_client.InitPrebidCache(server.URL) var labels = &metrics.Labels{} - if err := cacheVideoOnly(bids, ctx, &auction{cfg: cfg, syncersByBidder: syncersByBidder, gdprPerms: gdprPerms, metricsEngine: &metricsConf.DummyMetricsEngine{}}, labels); err != nil { + if err := cacheVideoOnly(bids, ctx, &auction{cfg: cfg, syncersByBidder: syncersByBidder, gdprPerms: gdprPerms, metricsEngine: &metricsConf.NilMetricsEngine{}}, labels); err != nil { t.Errorf("Prebid cache failed: %v \n", err) return } @@ -637,18 +637,18 @@ func TestWriteAuctionError(t *testing.T) { } func TestPanicRecovery(t *testing.T) { - dummy := auction{ + testAuction := auction{ cfg: nil, syncersByBidder: nil, gdprPerms: &auctionMockPermissions{ allowBidderSync: false, allowHostCookies: false, }, - metricsEngine: &metricsConf.DummyMetricsEngine{}, + metricsEngine: &metricsConf.NilMetricsEngine{}, } panicker := func(bidder *pbs.PBSBidder, blables metrics.AdapterLabels) { panic("panic!") } - recovered := dummy.recoverSafely(panicker) + recovered := testAuction.recoverSafely(panicker) recovered(nil, metrics.AdapterLabels{}) } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index ae3c036aa5e..656ee190ccd 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -45,7 +45,7 @@ func TestGoodAmpRequests(t *testing.T) { &mockAmpStoredReqFetcher{goodRequests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -99,7 +99,7 @@ func TestAMPPageInfo(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -197,7 +197,7 @@ func TestGDPRConsent(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -350,7 +350,7 @@ func TestCCPAConsent(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -461,7 +461,7 @@ func TestConsentWarnings(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -554,7 +554,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -606,7 +606,7 @@ func TestAMPSiteExt(t *testing.T) { &mockAmpStoredReqFetcher{stored}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), nil, nil, @@ -643,7 +643,7 @@ func TestAmpBadRequests(t *testing.T) { &mockAmpStoredReqFetcher{badRequests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -674,7 +674,7 @@ func TestAmpDebug(t *testing.T) { &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -747,7 +747,7 @@ func TestQueryParamOverrides(t *testing.T) { &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -900,7 +900,7 @@ func (s formatOverrideSpec) execute(t *testing.T) { &mockAmpStoredReqFetcher{requests}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -1340,7 +1340,7 @@ func AmpObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMe mockAmpFetcher, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize, GenerateRequestID: generateRequestID}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, logger, map[string]string{}, []byte{}, diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index f2e0fb2a9f2..2a1300d72d7 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -18,13 +18,13 @@ import ( "github.com/prebid/prebid-server/usersync" ) -// dummyServer returns the header bidding test ad. This response was scraped from a real appnexus server response. -func dummyServer(w http.ResponseWriter, r *http.Request) { +// benchmarkTestServer returns the header bidding test ad. This response was scraped from a real appnexus server response. +func benchmarkTestServer(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"id":"some-request-id","seatbid":[{"bid":[{"id":"4625436751433509010","impid":"my-imp-id","price":0.5,"adm":"\u003cscript type=\"application/javascript\" src=\"http://nym1-ib.adnxs.com/ab?e=wqT_3QKABqAAAwAAAwDWAAUBCM-OiNAFELuV09Pqi86EVRj6t-7QyLin_REqLQkAAAECCOA_EQEHNAAA4D8ZAAAAgOtR4D8hERIAKREJoDDy5vwEOL4HQL4HSAJQ1suTDljhgEhgAGiRQHixhQSAAQGKAQNVU0SSBQbwUpgBrAKgAfoBqAEBsAEAuAECwAEDyAEC0AEA2AEA4AEB8AEAigI6dWYoJ2EnLCA0OTQ0NzIsIDE1MTAwODIzODMpO3VmKCdyJywgMjk2ODExMTAsMh4A8JySAvkBIVR6WGNkQWk2MEljRUVOYkxrdzRZQUNEaGdFZ3dBRGdBUUFSSXZnZFE4dWI4QkZnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFFcGk0aURBQURnUDhFQktZdUlnd0FBNERfSkFTZlJKRUdtbi00XzJRRUFBQUFBQUFEd1AtQUJBUFVCBQ8oSmdDQUtBQ0FMVUMFEARMMAkI8ExNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEdXRDSEJMb0RDVTVaVFRJNk16STNOdy4umgItITh3aENuZzb8ALg0WUJJSUFRb0FEb0pUbGxOTWpvek1qYzPYAugH4ALH0wHyAhAKBkFEVl9JRBIGNCV1HPICEQoGQ1BHARMcBzE5Nzc5MzMBJwgFQ1AFE_B-ODUxMzU5NIADAYgDAZADAJgDFKADAaoDAMADrALIAwDYAwDgAwDoAwD4AwCABACSBAkvb3BlbnJ0YjKYBACoBACyBAwIABAAGAAgADAAOAC4BADABADIBADSBAlOWU0yOjMyNzfaBAIIAeAEAPAE1suTDogFAZgFAKAF_____wUDXAGqBQ9zb21lLXJlcXVlc3QtaWTABQDJBUmbTPA_0gUJCQAAAAAAAAAA2AUB4AUB\u0026s=61dc0e8770543def5a3a77b4589830d1274b26f1\u0026test=1\u0026pp=${AUCTION_PRICE}\u0026\"\u003e\u003c/script\u003e","adid":"29681110","adomain":["appnexus.com"],"iurl":"http://nym1-ib.adnxs.com/cr?id=29681110","cid":"958","crid":"29681110","w":300,"h":250,"ext":{"bidder":{"appnexus":{"brand_id":1,"auction_id":6127490747252132539,"bidder_id":2}}}}],"seat":"appnexus"}],"ext":{"debug":{"httpcalls":{"appnexus":[{"uri":"http://ib.adnxs.com/openrtb2","requestbody":"{\"id\":\"some-request-id\",\"imp\":[{\"id\":\"my-imp-id\",\"banner\":{\"format\":[{\"w\":300,\"h\":250},{\"w\":300,\"h\":600}]},\"ext\":{\"appnexus\":{\"placement_id\":12883451}}}],\"test\":1,\"tmax\":500}","responsebody":"{\"id\":\"some-request-id\",\"seatbid\":[{\"bid\":[{\"id\":\"4625436751433509010\",\"impid\":\"my-imp-id\",\"price\": 0.500000,\"adid\":\"29681110\",\"adm\":\"\u003cscript type=\\\"application/javascript\\\" src=\\\"http://nym1-ib.adnxs.com/ab?e=wqT_3QKABqAAAwAAAwDWAAUBCM-OiNAFELuV09Pqi86EVRj6t-7QyLin_REqLQkAAAECCOA_EQEHNAAA4D8ZAAAAgOtR4D8hERIAKREJoDDy5vwEOL4HQL4HSAJQ1suTDljhgEhgAGiRQHixhQSAAQGKAQNVU0SSBQbwUpgBrAKgAfoBqAEBsAEAuAECwAEDyAEC0AEA2AEA4AEB8AEAigI6dWYoJ2EnLCA0OTQ0NzIsIDE1MTAwODIzODMpO3VmKCdyJywgMjk2ODExMTAsMh4A8JySAvkBIVR6WGNkQWk2MEljRUVOYkxrdzRZQUNEaGdFZ3dBRGdBUUFSSXZnZFE4dWI4QkZnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFFcGk0aURBQURnUDhFQktZdUlnd0FBNERfSkFTZlJKRUdtbi00XzJRRUFBQUFBQUFEd1AtQUJBUFVCBQ8oSmdDQUtBQ0FMVUMFEARMMAkI8ExNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEdXRDSEJMb0RDVTVaVFRJNk16STNOdy4umgItITh3aENuZzb8ALg0WUJJSUFRb0FEb0pUbGxOTWpvek1qYzPYAugH4ALH0wHyAhAKBkFEVl9JRBIGNCV1HPICEQoGQ1BHARMcBzE5Nzc5MzMBJwgFQ1AFE_B-ODUxMzU5NIADAYgDAZADAJgDFKADAaoDAMADrALIAwDYAwDgAwDoAwD4AwCABACSBAkvb3BlbnJ0YjKYBACoBACyBAwIABAAGAAgADAAOAC4BADABADIBADSBAlOWU0yOjMyNzfaBAIIAeAEAPAE1suTDogFAZgFAKAF_____wUDXAGqBQ9zb21lLXJlcXVlc3QtaWTABQDJBUmbTPA_0gUJCQAAAAAAAAAA2AUB4AUB\u0026s=61dc0e8770543def5a3a77b4589830d1274b26f1\u0026test=1\u0026pp=${AUCTION_PRICE}\u0026\\\"\u003e\u003c/script\u003e\",\"adomain\":[\"appnexus.com\"],\"iurl\":\"http://nym1-ib.adnxs.com/cr?id=29681110\",\"cid\":\"958\",\"crid\":\"29681110\",\"h\": 250,\"w\": 300,\"ext\":{\"appnexus\":{\"brand_id\": 1,\"auction_id\": 6127490747252132539,\"bidder_id\": 2}}}],\"seat\":\"958\"}],\"bidid\":\"8271358638249766712\",\"cur\":\"USD\"}","status":200}]}},"responsetimemillis":{"appnexus":42}}}`)) } -// newDummyRequest returns a request which fetches the header bidding test ad. -func newDummyRequest() *http.Request { +// benchmarkBuildTestRequest returns a request which fetches the header bidding test ad. +func benchmarkBuildTestRequest() *http.Request { request, _ := http.NewRequest("POST", "/openrtb2/auction", strings.NewReader(`{ "id": "some-request-id", "imp": [ @@ -57,7 +57,7 @@ func newDummyRequest() *http.Request { // BenchmarkOpenrtbEndpoint measures the performance of the endpoint, mocking out the external server dependency. func BenchmarkOpenrtbEndpoint(b *testing.B) { - server := httptest.NewServer(http.HandlerFunc(dummyServer)) + server := httptest.NewServer(http.HandlerFunc(benchmarkTestServer)) defer server.Close() var infos config.BidderInfos @@ -67,7 +67,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { return } - nilMetrics := &metricsConfig.DummyMetricsEngine{} + nilMetrics := &metricsConfig.NilMetricsEngine{} adapters, adaptersErr := exchange.BuildAdapters(server.Client(), &config.Configuration{}, infos, nilMetrics) if adaptersErr != nil { @@ -102,6 +102,6 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { b.ResetTimer() for n := 0; n < b.N; n++ { - endpoint(httptest.NewRecorder(), newDummyRequest(), nil) + endpoint(httptest.NewRecorder(), benchmarkBuildTestRequest(), nil) } } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 5f4a2902b86..80124eec74f 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -436,7 +436,7 @@ func TestExplicitUserId(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -478,7 +478,7 @@ func doRequest(t *testing.T, test testCase) (int, string) { BlacklistedAcctMap: test.Config.getBlackListedAccountMap(), AccountRequired: test.Config.AccountRequired, }, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, []byte(test.Config.AliasJSON), @@ -542,7 +542,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, @@ -593,7 +593,7 @@ func TestNilExchange(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BuildBidderMap()) @@ -614,7 +614,7 @@ func TestNilValidator(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -636,7 +636,7 @@ func TestExchangeError(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -759,7 +759,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -954,7 +954,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -1109,7 +1109,7 @@ func TestStoredRequests(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1160,7 +1160,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1230,7 +1230,7 @@ func TestOversizedRequest(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody) - 1)}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1266,7 +1266,7 @@ func TestRequestSizeEdgeCase(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1300,7 +1300,7 @@ func TestNoEncoding(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -1376,7 +1376,7 @@ func TestContentType(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -1697,7 +1697,7 @@ func TestValidateImpExt(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(8096)}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."}, false, @@ -1744,7 +1744,7 @@ func TestCurrencyTrunc(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1789,7 +1789,7 @@ func TestCCPAInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1838,7 +1838,7 @@ func TestNoSaleInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1890,7 +1890,7 @@ func TestValidateSourceTID(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, cfg, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1932,7 +1932,7 @@ func TestSChainInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -2152,7 +2152,7 @@ func TestEidPermissionsInvalid(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -2403,7 +2403,7 @@ func TestIOS14EndToEnd(t *testing.T) { &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, @@ -2432,7 +2432,7 @@ func TestAuctionWarnings(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -2472,7 +2472,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) { empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: int64(len(reqBody))}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 26d9f028885..3163cd9d323 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1260,7 +1260,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps { &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1283,7 +1283,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, @@ -1306,7 +1306,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps { &mockVideoStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, - &metricsConfig.DummyMetricsEngine{}, + &metricsConfig.NilMetricsEngine{}, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 976946078c7..3388965fc09 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -234,7 +234,7 @@ func TestSetUIDEndpoint(t *testing.T) { }, } - metrics := &metricsConf.DummyMetricsEngine{} + metrics := &metricsConf.NilMetricsEngine{} for _, test := range testCases { response := doRequest(makeRequest(test.uri, test.existingSyncs), metrics, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed) @@ -379,7 +379,7 @@ func TestOptedOut(t *testing.T) { cookie.SetOptOut(true) addCookie(request, cookie) syncersBidderNameToKey := map[string]string{"pubmatic": "pubmatic"} - metrics := &metricsConf.DummyMetricsEngine{} + metrics := &metricsConf.NilMetricsEngine{} response := doRequest(request, metrics, syncersBidderNameToKey, true, false, false) assert.Equal(t, http.StatusUnauthorized, response.Code) diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index a8e8ef5e30a..b3a05cdc2d4 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -22,7 +22,7 @@ var ( func TestBuildAdapters(t *testing.T) { client := &http.Client{} - metricEngine := &metrics.DummyMetricsEngine{} + metricEngine := &metrics.NilMetricsEngine{} appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{}) appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled) diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 1a630a962c7..da31658e32d 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -91,7 +91,7 @@ func TestSingleBidder(t *testing.T) { } bidderImpl.bidResponse = mockBidderResponse - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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, false) @@ -170,7 +170,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { debugInfo := &config.DebugInfo{Allow: true} ctx := context.Background() - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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, false) @@ -210,7 +210,7 @@ func TestSetGPCHeader(t *testing.T) { debugInfo := &config.DebugInfo{Allow: true} ctx := context.Background() - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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) @@ -247,7 +247,7 @@ func TestSetGPCHeaderNil(t *testing.T) { debugInfo := &config.DebugInfo{Allow: true} ctx := context.Background() - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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) @@ -305,7 +305,7 @@ func TestMultiBidder(t *testing.T) { }}, bidResponse: mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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, true) @@ -346,7 +346,7 @@ func TestBidderTimeout(t *testing.T) { Bidder: &mixedMultiBidder{}, BidderName: openrtb_ext.BidderAppnexus, Client: server.Client(), - me: &metricsConfig.DummyMetricsEngine{}, + me: &metricsConfig.NilMetricsEngine{}, } callInfo := bidder.doRequest(ctx, &adapters.RequestData{ @@ -389,7 +389,7 @@ func TestConnectionClose(t *testing.T) { Bidder: &mixedMultiBidder{}, Client: server.Client(), BidderName: openrtb_ext.BidderAppnexus, - me: &metricsConfig.DummyMetricsEngine{}, + me: &metricsConfig.NilMetricsEngine{}, } callInfo := bidder.doRequest(context.Background(), &adapters.RequestData{ @@ -659,7 +659,7 @@ func TestMultiCurrencies(t *testing.T) { ) // Execute: - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -812,7 +812,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { } // Execute: - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBid, errs := bidder.requestBid( context.Background(), @@ -974,7 +974,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { } // Execute: - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter( &http.Client{}, mockedHTTPServer.URL, @@ -1284,7 +1284,7 @@ func TestMobileNativeTypes(t *testing.T) { }, bidResponse: tc.mockBidderResponse, } - bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBids, _ := bidder.requestBid( @@ -1307,7 +1307,7 @@ func TestMobileNativeTypes(t *testing.T) { } func TestErrorReporting(t *testing.T) { - bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, 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, false) if bids != nil { @@ -1631,7 +1631,7 @@ func TestTimeoutNotificationOff(t *testing.T) { Bidder: bidderImpl, Client: server.Client(), config: bidderAdapterConfig{Debug: config.Debug{}}, - me: &metricsConfig.DummyMetricsEngine{}, + me: &metricsConfig.NilMetricsEngine{}, } if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok { t.Error("Failed to cast bidder to a TimeoutBidder") @@ -1672,7 +1672,7 @@ func TestTimeoutNotificationOn(t *testing.T) { }, }, }, - me: &metricsConfig.DummyMetricsEngine{}, + me: &metricsConfig.NilMetricsEngine{}, } // Unwrap To Mimic exchange.go Casting Code diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 6258d12154f..9dcf9d66a7f 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -60,13 +60,13 @@ func TestNewExchange(t *testing.T) { t.Fatal(err) } - adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) for _, bidderName := range knownAdapters { if _, ok := e.adapterMap[bidderName]; !ok { t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName) @@ -108,13 +108,13 @@ func TestCharacterEscape(t *testing.T) { t.Fatal(err) } - adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, @@ -308,7 +308,7 @@ func TestDebugBehaviour(t *testing.T) { e := new(exchange) e.cache = &wellBehavedCache{} - e.me = &metricsConf.DummyMetricsEngine{} + e.me = &metricsConf.NilMetricsEngine{} e.gDPR = gdpr.AlwaysAllow{} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher @@ -319,7 +319,7 @@ func TestDebugBehaviour(t *testing.T) { for _, test := range testCases { e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}), + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}), } bidRequest.Test = test.in.test @@ -463,7 +463,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { e := new(exchange) e.cache = &wellBehavedCache{} - e.me = &metricsConf.DummyMetricsEngine{} + e.me = &metricsConf.NilMetricsEngine{} e.gDPR = gdpr.AlwaysAllow{} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher @@ -494,8 +494,8 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { } e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}), - openrtb_ext.BidderTelaria: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}), + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}), + openrtb_ext.BidderTelaria: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}), } // Run test outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog) @@ -619,7 +619,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) { e := new(exchange) e.cache = &wellBehavedCache{} - e.me = &metricsConf.DummyMetricsEngine{} + e.me = &metricsConf.NilMetricsEngine{} e.gDPR = gdpr.AlwaysAllow{} e.currencyConverter = mockCurrencyConverter e.categoriesFetcher = categoriesFetcher @@ -649,7 +649,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) { } e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), + openrtb_ext.BidderAppnexus: adaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), } // Set custom rates in extension @@ -713,13 +713,13 @@ func TestAdapterCurrency(t *testing.T) { // Initialize Real Exchange e := exchange{ cache: &wellBehavedCache{}, - me: &metricsConf.DummyMetricsEngine{}, + me: &metricsConf.NilMetricsEngine{}, 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), + openrtb_ext.BidderName("foo"): adaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil), }, } @@ -1100,10 +1100,10 @@ func TestReturnCreativeEndToEnd(t *testing.T) { e := new(exchange) e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), } e.cache = &wellBehavedCache{} - e.me = &metricsConf.DummyMetricsEngine{} + e.me = &metricsConf.NilMetricsEngine{} e.gDPR = gdpr.AlwaysAllow{} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher @@ -1196,13 +1196,13 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { t.Fatal(err) } - adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) pbc := pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine) - e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} @@ -1390,10 +1390,10 @@ func TestBidReturnsCreative(t *testing.T) { } e := new(exchange) e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), } e.cache = &wellBehavedCache{} - e.me = &metricsConf.DummyMetricsEngine{} + e.me = &metricsConf.NilMetricsEngine{} e.gDPR = gdpr.AlwaysAllow{} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) @@ -1550,13 +1550,13 @@ func TestBidResponseCurrency(t *testing.T) { t.Fatal(err) } - adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1695,7 +1695,7 @@ func TestBidResponseImpExtInfo(t *testing.T) { cfg := &config.Configuration{Adapters: make(map[string]config.Adapter, 1)} cfg.Adapters["appnexus"] = config.Adapter{Endpoint: "http://ib.adnxs.com"} - e := NewExchange(nil, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, nil, gdpr.AlwaysAllow{}, nil, nilCategoryFetcher{}).(*exchange) + e := NewExchange(nil, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdpr.AlwaysAllow{}, nil, nilCategoryFetcher{}).(*exchange) liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -1780,7 +1780,7 @@ func TestRaceIntegration(t *testing.T) { t.Fatal(err) } - adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) } @@ -1794,7 +1794,7 @@ func TestRaceIntegration(t *testing.T) { } debugLog := DebugLog{} - ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, &nilCategoryFetcher{}).(*exchange) + ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, &nilCategoryFetcher{}).(*exchange) _, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) @@ -1880,13 +1880,13 @@ func TestPanicRecovery(t *testing.T) { t.Fatal(err) } - adapters, adaptersErr := BuildAdapters(&http.Client{}, cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) + adapters, adaptersErr := BuildAdapters(&http.Client{}, cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) } currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) + e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) chBids := make(chan *bidResponseWrapper, 1) panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) { @@ -1947,7 +1947,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { t.Fatal(err) } - adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) } @@ -1959,7 +1959,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, categoriesFetcher).(*exchange) + e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, categoriesFetcher).(*exchange) e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index f38a6c0266c..8991a116624 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -88,7 +88,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op ex := &exchange{ adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), - me: &metricsConf.DummyMetricsEngine{}, + me: &metricsConf.NilMetricsEngine{}, cache: &wellBehavedCache{}, cacheTime: time.Duration(0), gDPR: gdpr.AlwaysAllow{}, @@ -143,7 +143,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServer adapterMap[bidder] = adaptBidder(&mockTargetingBidder{ mockServerURL: mockServerURL, bids: bids, - }, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) + }, client, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) } return adapterMap } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 14ebb8e9107..6360fc3b9d8 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -1936,22 +1936,39 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { } func TestRandomizeList(t *testing.T) { - adapters := make([]openrtb_ext.BidderName, 3) - adapters[0] = openrtb_ext.BidderName("dummy") - adapters[1] = openrtb_ext.BidderName("dummy2") - adapters[2] = openrtb_ext.BidderName("dummy3") - - randomizeList(adapters) + var ( + bidder1 = openrtb_ext.BidderName("bidder1") + bidder2 = openrtb_ext.BidderName("bidder2") + bidder3 = openrtb_ext.BidderName("bidder3") + ) - if len(adapters) != 3 { - t.Errorf("RandomizeList, expected a list of 3, found %d", len(adapters)) + testCases := []struct { + description string + bidders []openrtb_ext.BidderName + }{ + { + description: "None", + bidders: []openrtb_ext.BidderName{}, + }, + { + description: "One", + bidders: []openrtb_ext.BidderName{bidder1}, + }, + { + description: "Many", + bidders: []openrtb_ext.BidderName{bidder1, bidder2, bidder3}, + }, } - adapters = adapters[0:1] - randomizeList(adapters) + for _, test := range testCases { + biddersWorkingCopy := make([]openrtb_ext.BidderName, len(test.bidders)) + copy(biddersWorkingCopy, test.bidders) + + randomizeList(biddersWorkingCopy) - if len(adapters) != 1 { - t.Errorf("RandomizeList, expected a list of 1, found %d", len(adapters)) + // test all bidders are still present, ignoring order. we are testing the algorithm doesn't loose + // elements. we are not testing the random number generator itself. + assert.ElementsMatch(t, test.bidders, biddersWorkingCopy) } } diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 34d874d9553..51ba8cafe2f 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -47,7 +47,7 @@ func NewMetricsEngine(cfg *config.Configuration, adapterList []openrtb_ext.Bidde } else if len(engineList) == 1 { returnEngine.MetricsEngine = engineList[0] } else { - returnEngine.MetricsEngine = &DummyMetricsEngine{} + returnEngine.MetricsEngine = &NilMetricsEngine{} } return &returnEngine @@ -258,117 +258,118 @@ func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ex } } -// DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests) -type DummyMetricsEngine struct{} +// NilMetricsEngine implements the MetricsEngine interface where no metrics are actually captured. This is +// used if no metric backend is configured and also for tests. +type NilMetricsEngine struct{} // RecordRequest as a noop -func (me *DummyMetricsEngine) RecordRequest(labels metrics.Labels) { +func (me *NilMetricsEngine) RecordRequest(labels metrics.Labels) { } // RecordConnectionAccept as a noop -func (me *DummyMetricsEngine) RecordConnectionAccept(success bool) { +func (me *NilMetricsEngine) RecordConnectionAccept(success bool) { } // RecordConnectionClose as a noop -func (me *DummyMetricsEngine) RecordConnectionClose(success bool) { +func (me *NilMetricsEngine) RecordConnectionClose(success bool) { } // RecordImps as a noop -func (me *DummyMetricsEngine) RecordImps(implabels metrics.ImpLabels) { +func (me *NilMetricsEngine) RecordImps(implabels metrics.ImpLabels) { } // RecordLegacyImps as a noop -func (me *DummyMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { +func (me *NilMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { } // RecordRequestTime as a noop -func (me *DummyMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { +func (me *NilMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { } // RecordStoredDataFetchTime as a noop -func (me *DummyMetricsEngine) RecordStoredDataFetchTime(labels metrics.StoredDataLabels, length time.Duration) { +func (me *NilMetricsEngine) RecordStoredDataFetchTime(labels metrics.StoredDataLabels, length time.Duration) { } // RecordStoredDataError as a noop -func (me *DummyMetricsEngine) RecordStoredDataError(labels metrics.StoredDataLabels) { +func (me *NilMetricsEngine) RecordStoredDataError(labels metrics.StoredDataLabels) { } // RecordAdapterPanic as a noop -func (me *DummyMetricsEngine) RecordAdapterPanic(labels metrics.AdapterLabels) { +func (me *NilMetricsEngine) RecordAdapterPanic(labels metrics.AdapterLabels) { } // RecordAdapterRequest as a noop -func (me *DummyMetricsEngine) RecordAdapterRequest(labels metrics.AdapterLabels) { +func (me *NilMetricsEngine) RecordAdapterRequest(labels metrics.AdapterLabels) { } // RecordAdapterConnections as a noop -func (me *DummyMetricsEngine) RecordAdapterConnections(bidderName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) { +func (me *NilMetricsEngine) RecordAdapterConnections(bidderName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) { } // RecordDNSTime as a noop -func (me *DummyMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { +func (me *NilMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { } // RecordTLSHandshakeTime as a noop -func (me *DummyMetricsEngine) RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) { +func (me *NilMetricsEngine) RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) { } // RecordAdapterBidReceived as a noop -func (me *DummyMetricsEngine) RecordAdapterBidReceived(labels metrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { +func (me *NilMetricsEngine) RecordAdapterBidReceived(labels metrics.AdapterLabels, bidType openrtb_ext.BidType, hasAdm bool) { } // RecordAdapterPrice as a noop -func (me *DummyMetricsEngine) RecordAdapterPrice(labels metrics.AdapterLabels, cpm float64) { +func (me *NilMetricsEngine) RecordAdapterPrice(labels metrics.AdapterLabels, cpm float64) { } // RecordAdapterTime as a noop -func (me *DummyMetricsEngine) RecordAdapterTime(labels metrics.AdapterLabels, length time.Duration) { +func (me *NilMetricsEngine) RecordAdapterTime(labels metrics.AdapterLabels, length time.Duration) { } // RecordCookieSync as a noop -func (me *DummyMetricsEngine) RecordCookieSync(status metrics.CookieSyncStatus) { +func (me *NilMetricsEngine) RecordCookieSync(status metrics.CookieSyncStatus) { } // RecordSyncerRequest as a noop -func (me *DummyMetricsEngine) RecordSyncerRequest(key string, status metrics.SyncerCookieSyncStatus) { +func (me *NilMetricsEngine) RecordSyncerRequest(key string, status metrics.SyncerCookieSyncStatus) { } // RecordSetUid as a noop -func (me *DummyMetricsEngine) RecordSetUid(status metrics.SetUidStatus) { +func (me *NilMetricsEngine) RecordSetUid(status metrics.SetUidStatus) { } // RecordSyncerSet as a noop -func (me *DummyMetricsEngine) RecordSyncerSet(key string, status metrics.SyncerSetUidStatus) { +func (me *NilMetricsEngine) RecordSyncerSet(key string, status metrics.SyncerSetUidStatus) { } // RecordStoredReqCacheResult as a noop -func (me *DummyMetricsEngine) RecordStoredReqCacheResult(cacheResult metrics.CacheResult, inc int) { +func (me *NilMetricsEngine) RecordStoredReqCacheResult(cacheResult metrics.CacheResult, inc int) { } // RecordStoredImpCacheResult as a noop -func (me *DummyMetricsEngine) RecordStoredImpCacheResult(cacheResult metrics.CacheResult, inc int) { +func (me *NilMetricsEngine) RecordStoredImpCacheResult(cacheResult metrics.CacheResult, inc int) { } // RecordAccountCacheResult as a noop -func (me *DummyMetricsEngine) RecordAccountCacheResult(cacheResult metrics.CacheResult, inc int) { +func (me *NilMetricsEngine) RecordAccountCacheResult(cacheResult metrics.CacheResult, inc int) { } // RecordPrebidCacheRequestTime as a noop -func (me *DummyMetricsEngine) RecordPrebidCacheRequestTime(success bool, length time.Duration) { +func (me *NilMetricsEngine) RecordPrebidCacheRequestTime(success bool, length time.Duration) { } // RecordRequestQueueTime as a noop -func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType metrics.RequestType, length time.Duration) { +func (me *NilMetricsEngine) RecordRequestQueueTime(success bool, requestType metrics.RequestType, length time.Duration) { } // RecordTimeoutNotice as a noop -func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) { +func (me *NilMetricsEngine) RecordTimeoutNotice(success bool) { } // RecordRequestPrivacy as a noop -func (me *DummyMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels) { +func (me *NilMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels) { } // RecordAdapterGDPRRequestBlocked as a noop -func (me *DummyMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { +func (me *NilMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { } diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 04c888679e7..0d6bcdb922d 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -12,14 +12,14 @@ import ( ) // Start a simple test to insure we get valid MetricsEngines for various configurations -func TestDummyMetricsEngine(t *testing.T) { +func TestNilMetricsEngine(t *testing.T) { cfg := mainConfig.Configuration{} adapterList := make([]openrtb_ext.BidderName, 0, 2) syncerKeys := []string{"keyA", "keyB"} testEngine := NewMetricsEngine(&cfg, adapterList, syncerKeys) - _, ok := testEngine.MetricsEngine.(*DummyMetricsEngine) + _, ok := testEngine.MetricsEngine.(*NilMetricsEngine) if !ok { - t.Error("Expected a DummyMetricsEngine, but didn't get it") + t.Error("Expected a NilMetricsEngine, but didn't get it") } } @@ -43,7 +43,7 @@ func TestMultiMetricsEngine(t *testing.T) { goEngine := metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, mainConfig.DisabledMetrics{}, nil) engineList := make(MultiMetricsEngine, 2) engineList[0] = goEngine - engineList[1] = &DummyMetricsEngine{} + engineList[1] = &NilMetricsEngine{} var metricsEngine metrics.MetricsEngine metricsEngine = &engineList labels := metrics.Labels{ diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index 1ba30a6faab..60237bbbb27 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -257,7 +257,7 @@ func TestStripCacheHostAndPath(t *testing.T) { }, } for _, test := range testInput { - cacheClient := NewClient(&http.Client{}, &inCacheURL, &test.inExtCacheURL, &metricsConf.DummyMetricsEngine{}) + cacheClient := NewClient(&http.Client{}, &inCacheURL, &test.inExtCacheURL, &metricsConf.NilMetricsEngine{}) scheme, host, path := cacheClient.GetExtCacheData() assert.Equal(t, test.expectedScheme, scheme) diff --git a/stored_requests/events/events_test.go b/stored_requests/events/events_test.go index f3483705e86..c943dd69b55 100644 --- a/stored_requests/events/events_test.go +++ b/stored_requests/events/events_test.go @@ -12,7 +12,7 @@ import ( ) func TestListen(t *testing.T) { - ep := &dummyProducer{ + ep := &fakeProducer{ saves: make(chan Save), invalidations: make(chan Invalidation), } @@ -81,15 +81,15 @@ func TestListen(t *testing.T) { } } -type dummyProducer struct { +type fakeProducer struct { saves chan Save invalidations chan Invalidation } -func (p *dummyProducer) Saves() <-chan Save { +func (p *fakeProducer) Saves() <-chan Save { return p.saves } -func (p *dummyProducer) Invalidations() <-chan Invalidation { +func (p *fakeProducer) Invalidations() <-chan Invalidation { return p.invalidations } From 1e486d521c258ce5fe3c65b74d11cd7c7edee76e Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Tue, 28 Sep 2021 12:27:34 -0700 Subject: [PATCH 100/140] Adding Support for Macro {{UUID}} to Support Feature: Generate Unique Bidrequest ID (#2000) * Adding support for macro {{UUID}} in id field * Add macro option for amp auctions, added extra tests for generating uuid * Updated so macro ID check is from stored request * Added case sensitivity macro id test cases --- endpoints/openrtb2/amp_auction.go | 2 +- endpoints/openrtb2/amp_auction_test.go | 14 +++- endpoints/openrtb2/auction.go | 18 ++++- endpoints/openrtb2/auction_test.go | 92 +++++++++++++++++++++++--- 4 files changed, 114 insertions(+), 12 deletions(-) diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 159e8a15922..123b576aa4c 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -348,7 +348,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return } - if deps.cfg.GenerateRequestID { + if deps.cfg.GenerateRequestID || req.ID == "{{UUID}}" { newBidRequestId, err := deps.uuidGenerator.Generate() if err != nil { errs = []error{err} diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index 656ee190ccd..f37c60f709e 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -1304,11 +1304,23 @@ func TestIdGeneration(t *testing.T) { expectedID: "ThisID", }, { - description: "The givenGenerateRequestID flag is true, and if the id field isn't included in the stored request", + description: "The givenGenerateRequestID flag is true, and the id field isn't included in the stored request, we should still generate a uuid", givenInStoredRequest: json.RawMessage(`{"site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`), givenGenerateRequestID: true, expectedID: uuid, }, + { + description: "The givenGenerateRequestID flag is false, but id field is the macro option {{UUID}}, we should generate a uuid", + givenInStoredRequest: json.RawMessage(`{"id":"{{UUID}}","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`), + givenGenerateRequestID: false, + expectedID: uuid, + }, + { + description: "Macro ID case sensitivity check. The id is {{uuid}}, but we should only generate an id if it's all uppercase {{UUID}}. So the ID shouldn't change.", + givenInStoredRequest: json.RawMessage(`{"id":"{{uuid}}","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`), + givenGenerateRequestID: false, + expectedID: "{{uuid}}", + }, } request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=test", nil) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 44b05f4057c..790a8d6482d 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1356,14 +1356,19 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson } storedRequests, storedImps, errs := deps.storedReqFetcher.FetchRequests(ctx, storedReqIds, impStoredReqIds) + if len(errs) != 0 { return nil, nil, errs } + bidRequestID, err := getBidRequestID(storedRequests[storedBidRequestId]) + if err != nil { + return nil, nil, []error{err} + } // Apply the Stored BidRequest, if it exists resolvedRequest := requestJson - if deps.cfg.GenerateRequestID { + if deps.cfg.GenerateRequestID || bidRequestID == "{{UUID}}" { isAppRequest, err := checkIfAppRequest(requestJson) if err != nil { return nil, nil, []error{err} @@ -1506,6 +1511,17 @@ func getStoredRequestId(data []byte) (string, bool, error) { return string(storedRequestId), true, nil } +func getBidRequestID(data json.RawMessage) (string, error) { + bidRequestID, dataType, _, err := jsonparser.Get(data, "id") + if dataType == jsonparser.NotExist { + return "", nil + } + if err != nil { + return "", err + } + return string(bidRequestID), nil +} + // setIPImplicitly sets the IP address on bidReq, if it's not explicitly defined and we can figure it out. func setIPImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest, ipValidator iputil.IPValidator) { if bidReq.Device == nil || (bidReq.Device.IP == "" && bidReq.Device.IPv6 == "") { diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 80124eec74f..a259719ba8a 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1180,29 +1180,47 @@ func TestStoredRequestGenerateUuid(t *testing.T) { expectedID string }{ { - description: "GenerateRequestID is true, rawData is an app request, and stored bid we should generate uuid", - givenRawData: testStoredRequestsUuid[2], + description: "GenerateRequestID is true, rawData is an app request and has stored bid request we should generate uuid", + givenRawData: testBidRequests[2], givenGenerateRequestID: true, expectedID: uuid, }, + { + description: "GenerateRequestID is true, rawData is an app request, has stored bid, and stored bidrequestID is not the macro {{UUID}}, we should generate uuid", + givenRawData: testBidRequests[3], + givenGenerateRequestID: true, + expectedID: uuid, + }, + { + description: "GenerateRequestID is false, rawData is an app request and has stored bid, but stored bidrequestID is the macro {{UUID}}, so we should generate uuid", + givenRawData: testBidRequests[4], + givenGenerateRequestID: false, + expectedID: uuid, + }, { description: "GenerateRequestID is true, rawData is an app request, but no stored bid, we should not generate uuid", - givenRawData: testStoredRequestsUuid[0], + givenRawData: testBidRequests[0], givenGenerateRequestID: true, expectedID: "ThisID", }, { - description: "GenerateRequestID is false so we should not generate uuid", - givenRawData: testStoredRequestsUuid[0], + description: "GenerateRequestID is false and macro ID is not present, so we should not generate uuid", + givenRawData: testBidRequests[0], givenGenerateRequestID: false, expectedID: "ThisID", }, { description: "GenerateRequestID is true, but rawData is a site request, we should not generate uuid", - givenRawData: testStoredRequestsUuid[1], + givenRawData: testBidRequests[1], givenGenerateRequestID: true, expectedID: "ThisID", }, + { + description: "Macro ID {{UUID}} case sensitivity check meaning a macro that is lowercase {{uuid}} shouldn't generate a uuid", + givenRawData: testBidRequests[2], + givenGenerateRequestID: false, + expectedID: "ThisID", + }, } for _, test := range testCases { @@ -3099,8 +3117,10 @@ func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequ // first below is valid JSON // second below is identical to first but with extra '}' for invalid JSON var testStoredRequestData = map[string]json.RawMessage{ + "1": json.RawMessage(`{"id": "{{UUID}}"}`), "2": json.RawMessage(`{ -"tmax": 500, + "id": "{{uuid}}", + "tmax": 500, "ext": { "prebid": { "targeting": { @@ -3110,7 +3130,7 @@ var testStoredRequestData = map[string]json.RawMessage{ } }`), "3": json.RawMessage(`{ -"tmax": 500, + "tmax": 500, "ext": { "prebid": { "targeting": { @@ -3545,7 +3565,7 @@ var testStoredImps = []string{ ``, } -var testStoredRequestsUuid = []string{ +var testBidRequests = []string{ `{ "id": "ThisID", "app": { @@ -3642,6 +3662,60 @@ var testStoredRequestsUuid = []string{ } } }`, + `{ + "id": "ThisID", + "app": { + "id": "123" + }, + "imp": [ + { + "ext": { + "prebid": { + "storedrequest": { + "id": "2" + }, + "options": { + "echovideoattrs": false + } + } + } + } + ], + "ext": { + "prebid": { + "storedrequest": { + "id": "2" + } + } + } + }`, + `{ + "id": "ThisID", + "app": { + "id": "123" + }, + "imp": [ + { + "ext": { + "prebid": { + "storedrequest": { + "id": "1" + }, + "options": { + "echovideoattrs": false + } + } + } + } + ], + "ext": { + "prebid": { + "storedrequest": { + "id": "1" + } + } + } + }`, } type mockStoredReqFetcher struct { From 4908326038b776727bfa3b8b5f8a401246208cfe Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Wed, 29 Sep 2021 10:55:10 -0400 Subject: [PATCH 101/140] Version Endpoint (#2009) --- endpoints/version.go | 42 ++++++++++++------------ endpoints/version_test.go | 67 ++++++++++++++++++++------------------- router/router.go | 2 ++ 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/endpoints/version.go b/endpoints/version.go index 514791e20b8..00d894963e6 100644 --- a/endpoints/version.go +++ b/endpoints/version.go @@ -7,31 +7,33 @@ import ( "github.com/golang/glog" ) -type versionModel struct { - Version string `json:"version"` - Revision string `json:"revision"` -} +const versionEndpointValueNotSet = "not-set" // NewVersionEndpoint returns the latest git tag as the version and commit hash as the revision from which the binary was built -func NewVersionEndpoint(version string, revision string) http.HandlerFunc { - if version == "" { - version = "not-set" - } - if revision == "" { - revision = "not-set" +func NewVersionEndpoint(version, revision string) http.HandlerFunc { + response, err := prepareVersionEndpointResponse(version, revision) + if err != nil { + glog.Fatalf("error creating /version endpoint response: %v", err) } return func(w http.ResponseWriter, _ *http.Request) { - jsonOutput, err := json.Marshal(versionModel{ - Version: version, - Revision: revision, - }) - if err != nil { - glog.Errorf("/version Critical error when trying to marshal versionModel: %v", err) - w.WriteHeader(http.StatusInternalServerError) - return - } + w.Write(response) + } +} - w.Write(jsonOutput) +func prepareVersionEndpointResponse(version, revision string) (json.RawMessage, error) { + if version == "" { + version = versionEndpointValueNotSet } + if revision == "" { + revision = versionEndpointValueNotSet + } + + return json.Marshal(struct { + Revision string `json:"revision"` + Version string `json:"version"` + }{ + Revision: revision, + Version: version, + }) } diff --git a/endpoints/version_test.go b/endpoints/version_test.go index ecddf532e8f..5629bea2ad9 100644 --- a/endpoints/version_test.go +++ b/endpoints/version_test.go @@ -1,52 +1,55 @@ package endpoints import ( - "encoding/json" "io/ioutil" "net/http/httptest" - "reflect" "testing" + + "github.com/stretchr/testify/assert" ) func TestVersion(t *testing.T) { - // Setup: var testCases = []struct { - version string - revision string - expected string + description string + version string + revision string + expected string }{ - {"", "", `{"version":"not-set","revision":"not-set"}`}, - {"abc", "def", `{"version":"abc","revision" :"def"}`}, - {"1.2.3", "d6cd1e2bd19e03a81132a23b2025920577f84e37", `{"version":"1.2.3","revision":"d6cd1e2bd19e03a81132a23b2025920577f84e37"}`}, + { + description: "Empty", + version: "", + revision: "", + expected: `{"revision":"not-set","version":"not-set"}`, + }, + { + description: "Version Only", + version: "1.2.3", + revision: "", + expected: `{"revision":"not-set","version":"1.2.3"}`, + }, + { + description: "Revision Only", + version: "", + revision: "d6cd1e2bd19e03a81132a23b2025920577f84e37", + expected: `{"revision":"d6cd1e2bd19e03a81132a23b2025920577f84e37","version":"not-set"}`, + }, + { + description: "Fully Populated", + version: "1.2.3", + revision: "d6cd1e2bd19e03a81132a23b2025920577f84e37", + expected: `{"revision":"d6cd1e2bd19e03a81132a23b2025920577f84e37","version":"1.2.3"}`, + }, } - for _, tc := range testCases { - - handler := NewVersionEndpoint(tc.version, tc.revision) + for _, test := range testCases { + handler := NewVersionEndpoint(test.version, test.revision) w := httptest.NewRecorder() - // Execute: handler(w, nil) - // Verify: - var result, expected versionModel - responseBodyBytes, err := ioutil.ReadAll(w.Body) - if err != nil { - t.Errorf("Error reading response body bytes: %s", err) - } - err = json.Unmarshal(responseBodyBytes, &result) - if err != nil { - t.Errorf("Bad response body. Expected: %s, got an error %s", tc.expected, err) - } - - err = json.Unmarshal([]byte(tc.expected), &expected) - if err != nil { - t.Errorf("Error while trying to unmarshal expected result JSON") - } - - if !reflect.DeepEqual(expected, result) { - responseBodyString := string(responseBodyBytes) - t.Errorf("Bad response body. Expected: %s, got %s", tc.expected, responseBodyString) + response, err := ioutil.ReadAll(w.Result().Body) + if assert.NoError(t, err, test.description+":read") { + assert.JSONEq(t, test.expected, string(response), test.description+":response") } } } diff --git a/router/router.go b/router/router.go index 17145b5a1e7..62eeedac4be 100644 --- a/router/router.go +++ b/router/router.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/endpoints/events" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/prebid-server/version" "github.com/prebid/prebid-server/metrics" @@ -310,6 +311,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, activeBidders).Handle) r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse)) r.GET("/", serveIndex) + r.Handler("GET", "/version", endpoints.NewVersionEndpoint(version.Ver, version.Rev)) r.ServeFiles("/static/*filepath", http.Dir("static")) // vtrack endpoint From ed352b9141f8cbd443f4bab597f3521d175bd242 Mon Sep 17 00:00:00 2001 From: BertiBauer <14088844+BertiBauer@users.noreply.github.com> Date: Thu, 30 Sep 2021 16:06:55 +0200 Subject: [PATCH 102/140] Yieldlab: Video adm and nurl (#2015) --- adapters/yieldlab/const.go | 1 + adapters/yieldlab/yieldlab.go | 6 +++++- adapters/yieldlab/yieldlabtest/exemplary/video.json | 1 + adapters/yieldlab/yieldlabtest/exemplary/video_app.json | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/adapters/yieldlab/const.go b/adapters/yieldlab/const.go index cb66cb6db17..183461392f9 100644 --- a/adapters/yieldlab/const.go +++ b/adapters/yieldlab/const.go @@ -4,4 +4,5 @@ const adSlotIdSeparator = "," const adsizeSeparator = "x" const adSourceBanner = "" const adSourceURL = "https://ad.yieldlab.net/d/%v/%v/%v?%v" +const vastMarkup = "Yieldlab" const creativeID = "%v%v%v" diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go index 66f12c8bcbe..2c9892aa33a 100644 --- a/adapters/yieldlab/yieldlab.go +++ b/adapters/yieldlab/yieldlab.go @@ -250,7 +250,7 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa if imp.Video != nil { bidType = openrtb_ext.BidTypeVideo responseBid.NURL = a.makeAdSourceURL(internalRequest, req, bid) - + responseBid.AdM = a.makeVast(internalRequest, req, bid) } else if imp.Banner != nil { bidType = openrtb_ext.BidTypeBanner responseBid.AdM = a.makeBannerAdSource(internalRequest, req, bid) @@ -293,6 +293,10 @@ func (a *YieldlabAdapter) makeBannerAdSource(req *openrtb2.BidRequest, ext *open return fmt.Sprintf(adSourceBanner, a.makeAdSourceURL(req, ext, res)) } +func (a *YieldlabAdapter) makeVast(req *openrtb2.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { + return fmt.Sprintf(vastMarkup, ext.AdslotID, a.makeAdSourceURL(req, ext, res)) +} + func (a *YieldlabAdapter) makeAdSourceURL(req *openrtb2.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { val := url.Values{} val.Set("ts", a.cacheBuster()) diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video.json b/adapters/yieldlab/yieldlabtest/exemplary/video.json index 9e970ae79b5..b15d63a92e0 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/video.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/video.json @@ -124,6 +124,7 @@ "id": "12345", "impid": "test-imp-id", "nurl": "https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing", + "adm": "Yieldlab", "price": 2.01, "w": 728, "h": 90 diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video_app.json b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json index 67d526b3400..17d96ddcb08 100644 --- a/adapters/yieldlab/yieldlabtest/exemplary/video_app.json +++ b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json @@ -124,6 +124,7 @@ "id": "12345", "impid": "test-imp-id", "nurl": "https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing", + "adm": "Yieldlab", "price": 2.01, "w": 728, "h": 90 From c13f4f8ae55f1143b18eb9e804380fdaaedddc9c Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 30 Sep 2021 11:21:22 -0400 Subject: [PATCH 103/140] Option To Disable Legacy /auction Endpoint (#2013) --- config/config.go | 5 +++++ config/config_test.go | 3 +++ router/router.go | 5 ++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 75203b9f89d..ce7d85fe160 100644 --- a/config/config.go +++ b/config/config.go @@ -85,6 +85,10 @@ type Configuration struct { GenerateBidID bool `mapstructure:"generate_bid_id"` // GenerateRequestID overrides the bidrequest.id in an AMP Request or an App Stored Request with a generated UUID if set to true. The default is false. GenerateRequestID bool `mapstructure:"generate_request_id"` + + // EnableLegacyAuction specifies if the original /auction endpoint with a custom PBS data model is allowed + // by the host. + EnableLegacyAuction bool `mapstructure:"enable_legacy_auction"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -948,6 +952,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("auto_gen_source_tid", true) v.SetDefault("generate_bid_id", false) v.SetDefault("generate_request_id", false) + v.SetDefault("enable_legacy_auction", true) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") diff --git a/config/config_test.go b/config/config_test.go index 03ad88461e2..ac7f4a2d747 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -144,6 +144,7 @@ func TestDefaults(t *testing.T) { 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) + cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, true) //Assert purpose VendorExceptionMap hash tables were built correctly expectedTCF2 := TCF2{ @@ -353,6 +354,7 @@ request_validation: ipv4_private_networks: ["1.1.1.0/24"] ipv6_private_networks: ["1111::/16", "2222::/16"] generate_bid_id: true +enable_legacy_auction: false `) var adapterExtraInfoConfig = []byte(` @@ -577,6 +579,7 @@ func TestFullConfig(t *testing.T) { 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, "") + cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, false) } func TestUnmarshalAdapterExtraInfo(t *testing.T) { diff --git a/router/router.go b/router/router.go index 62eeedac4be..90074753a5b 100644 --- a/router/router.go +++ b/router/router.go @@ -301,7 +301,10 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, metrics.ReqTypeVideo) } - r.POST("/auction", endpoints.Auction(cfg, syncersByBidder, gdprPerms, r.MetricsEngine, dataCache, exchanges)) + if cfg.EnableLegacyAuction { + r.POST("/auction", endpoints.Auction(cfg, syncersByBidder, gdprPerms, r.MetricsEngine, dataCache, exchanges)) + } + r.POST("/openrtb2/auction", openrtbEndpoint) r.POST("/openrtb2/video", videoEndpoint) r.GET("/openrtb2/amp", ampEndpoint) From f81b8dd917a7ff611c48288023a0e2ff2af5087b Mon Sep 17 00:00:00 2001 From: opeledtremor <83907615+opeledtremor@users.noreply.github.com> Date: Mon, 4 Oct 2021 18:58:55 +0300 Subject: [PATCH 104/140] Unruly r1 consolidated adapter (#1997) --- adapters/unruly/params_test.go | 13 +- adapters/unruly/unruly.go | 193 +++++++++-------- adapters/unruly/unruly_test.go | 195 +---------------- .../exemplary/banner-and-video-app.json | 201 ++++++++++++++++++ .../exemplary/banner-and-video-gdpr.json | 172 +++++++++++++++ .../exemplary/banner-and-video-site.json | 180 ++++++++++++++++ .../exemplary/banner-and-video.json | 181 ++++++++++++++++ .../unrulytest/exemplary/simple-banner.json | 109 ++++++++++ .../unrulytest/exemplary/simple-video.json | 104 +++++++++ .../unruly/unrulytest/exemplary/video.json | 116 ---------- .../supplemental/missing-extension.json | 34 +++ .../supplemental/no-matching-impid.json | 94 ++++++++ .../supplemental/status-code-204.json | 69 ++++++ .../supplemental/status-code-400.json | 87 ++++++++ .../supplemental/status-code-401.json | 74 +++++++ config/config.go | 2 +- openrtb_ext/imp_unruly.go | 3 +- static/bidder-info/unruly.yaml | 10 +- static/bidder-params/unruly.json | 8 +- 19 files changed, 1423 insertions(+), 422 deletions(-) create mode 100644 adapters/unruly/unrulytest/exemplary/banner-and-video-app.json create mode 100644 adapters/unruly/unrulytest/exemplary/banner-and-video-gdpr.json create mode 100644 adapters/unruly/unrulytest/exemplary/banner-and-video-site.json create mode 100644 adapters/unruly/unrulytest/exemplary/banner-and-video.json create mode 100644 adapters/unruly/unrulytest/exemplary/simple-banner.json create mode 100644 adapters/unruly/unrulytest/exemplary/simple-video.json delete mode 100644 adapters/unruly/unrulytest/exemplary/video.json create mode 100644 adapters/unruly/unrulytest/supplemental/missing-extension.json create mode 100644 adapters/unruly/unrulytest/supplemental/no-matching-impid.json create mode 100644 adapters/unruly/unrulytest/supplemental/status-code-204.json create mode 100644 adapters/unruly/unrulytest/supplemental/status-code-400.json create mode 100644 adapters/unruly/unrulytest/supplemental/status-code-401.json diff --git a/adapters/unruly/params_test.go b/adapters/unruly/params_test.go index 9b8f3110912..b7907f65b63 100644 --- a/adapters/unruly/params_test.go +++ b/adapters/unruly/params_test.go @@ -34,14 +34,13 @@ func TestInvalidParams(t *testing.T) { } var validParams = []string{ - `{"uuid": "123", "siteid": "abc"}`, + `{"siteid": 123}`, } var invalidParams = []string{ - `{}`, // Missing required uuid + siteId - `{"siteId": "123"}`, // Missing required uuid - `{"uuid": "123"}`, // Missing required siteId - `{"uuid": 123, "siteid": "abc"}`, // Wrong uuid data type - `{"uuid": "123", "siteid": 123}`, // Wrong siteid data type - `{"UUID": "123", "SiteId": "abc"}`, // Invalid capitalization (json is case sensitive) + `{}`, // Missing required siteId + `{"siteid": "123"}`, // Invalid siteId type + `{"uuid": "123"}`, // Missing required siteId + `{"SiteId": "abc"}`, // Invalid capitalization (json is case sensitive) + `{"siteId": []}`, // Invalid siteid data type } diff --git a/adapters/unruly/unruly.go b/adapters/unruly/unruly.go index 0077fae4df5..40b8fe66bd6 100644 --- a/adapters/unruly/unruly.go +++ b/adapters/unruly/unruly.go @@ -12,137 +12,146 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -type UnrulyAdapter struct { - URI string +type adapter struct { + endPoint string } // Builder builds a new instance of the Unruly adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - bidder := &UnrulyAdapter{ - URI: config.Endpoint, + bidder := &adapter{ + endPoint: config.Endpoint, } return bidder, nil } -func (a *UnrulyAdapter) ReplaceImp(imp openrtb2.Imp, request *openrtb2.BidRequest) *openrtb2.BidRequest { - reqCopy := *request - reqCopy.Imp = append(make([]openrtb2.Imp, 0, 1), imp) - return &reqCopy -} +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + errs := make([]error, 0, len(request.Imp)) -func (a *UnrulyAdapter) BuildRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { - reqJSON, err := json.Marshal(request) - if err != nil { - return nil, []error{err} + request, errs = a.preProcess(request, errs) + if request != nil { + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + if a.endPoint != "" { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.endPoint, + Body: reqJSON, + Headers: headers, + }}, errs + } } - - return &adapters.RequestData{ - Method: "POST", - Uri: a.URI, - Body: reqJSON, - Headers: AddHeadersToRequest(), - }, nil + return nil, errs } -func AddHeadersToRequest() http.Header { - headers := http.Header{} - headers.Add("Content-Type", "application/json;charset=utf-8") - headers.Add("Accept", "application/json") - headers.Add("X-Unruly-Origin", "Prebid-Server") - return headers -} - -func (a *UnrulyAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var errs []error - var adapterRequests []*adapters.RequestData - for _, imp := range request.Imp { - impWithUnrulyExt, err := convertBidderNameInExt(&imp) - if err != nil { - errs = append(errs, err) - } else { - newRequest := a.ReplaceImp(*impWithUnrulyExt, request) - adapterReq, errors := a.BuildRequest(newRequest) - if adapterReq != nil { - adapterRequests = append(adapterRequests, adapterReq) +func (a *adapter) preProcess(req *openrtb2.BidRequest, errors []error) (*openrtb2.BidRequest, []error) { + numRequests := len(req.Imp) + for i := 0; i < numRequests; i++ { + imp := req.Imp[i] + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("ext data not provided in imp id=%s. Abort all Request", imp.ID), + } + errors = append(errors, err) + return nil, errors + } + var unrulyExt openrtb_ext.ExtImpUnruly + if err := json.Unmarshal(bidderExt.Bidder, &unrulyExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("siteid not provided in imp id=%s. Abort all Request", imp.ID), } - errs = append(errs, errors...) + errors = append(errors, err) + return nil, errors + } + unrulyExtCopy, err := json.Marshal(&unrulyExt) + if err != nil { + errors = append(errors, err) + return nil, errors } + bidderExtCopy := struct { + Bidder json.RawMessage `json:"bidder,omitempty"` + }{unrulyExtCopy} + impExtCopy, err := json.Marshal(&bidderExtCopy) + if err != nil { + errors = append(errors, err) + return nil, errors + } + imp.Ext = impExtCopy + req.Imp[i] = imp } - return adapterRequests, errs + + return req, errors } -func getMediaTypeForImpWithId(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { - for _, imp := range imps { - if imp.ID == impID { - return openrtb_ext.BidTypeVideo, 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 } - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), + + 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), + }} } -} -func CheckResponse(response *adapters.ResponseData) error { if response.StatusCode != http.StatusOK { - return &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), - } + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), + }} } - return nil -} - -func convertToAdapterBidResponse(response *adapters.ResponseData, internalRequest *openrtb2.BidRequest) (*adapters.BidderResponse, []error) { - var errs []error var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { - return nil, []error{err} + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("bad server response: %d. ", err), + }} } + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + var errs []error for _, sb := range bidResp.SeatBid { for i := range sb.Bid { - bidType, err := getMediaTypeForImpWithId(sb.Bid[i].ImpID, internalRequest.Imp) + var bidType, err = getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) if err != nil { - errs = append(errs, err) + errs = append(errs, err...) } else { - b := &adapters.TypedBid{ + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &sb.Bid[i], BidType: bidType, - } - bidResponse.Bids = append(bidResponse.Bids, b) + }) } } } + return bidResponse, errs } -func convertBidderNameInExt(imp *openrtb2.Imp) (*openrtb2.Imp, error) { - var bidderExt adapters.ExtImpBidder - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return nil, err - } - var unrulyExt openrtb_ext.ExtImpUnruly - if err := json.Unmarshal(bidderExt.Bidder, &unrulyExt); err != nil { - return nil, err - } - var impExtUnruly = ImpExtUnruly{Unruly: openrtb_ext.ExtImpUnruly{ - SiteID: unrulyExt.SiteID, - UUID: unrulyExt.UUID, - }} - bytes, err := json.Marshal(impExtUnruly) - if err != nil { - return nil, err +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) (openrtb_ext.BidType, []error) { + var errs []error + var noMatchingImps []string + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impId { + if imp.Banner != nil { + mediaType = openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } else { + errs = append(errs, fmt.Errorf("bid responses mediaType didn't match supported mediaTypes")) + } + return mediaType, errs + } else { + noMatchingImps = append(noMatchingImps, imp.ID) + } } - imp.Ext = bytes - return imp, nil -} -func (a *UnrulyAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - if err := CheckResponse(response); err != nil { - return nil, []error{err} - } - return convertToAdapterBidResponse(response, internalRequest) -} + errs = append(errs, fmt.Errorf("Bid response imp ID %s not found in bid request containing imps %v\n", impId, noMatchingImps)) -type ImpExtUnruly struct { - Unruly openrtb_ext.ExtImpUnruly `json:"unruly"` + return mediaType, errs } diff --git a/adapters/unruly/unruly_test.go b/adapters/unruly/unruly_test.go index 445745c102d..c43e509941e 100644 --- a/adapters/unruly/unruly_test.go +++ b/adapters/unruly/unruly_test.go @@ -1,24 +1,16 @@ package unruly import ( - "encoding/json" - "fmt" - "net/http" - "reflect" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderUnruly, config.Adapter{ - Endpoint: "http://targeting.unrulymedia.com/openrtb/2.2"}) + Endpoint: "http://targeting.unrulymedia.com/unruly_prebid_server"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -26,188 +18,3 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "unrulytest", bidder) } - -func TestReturnsNewUnrulyBidderWithParams(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderUnruly, config.Adapter{ - Endpoint: "http://mockEndpoint.com"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - bidderUnruly := bidder.(*UnrulyAdapter) - - assert.Equal(t, "http://mockEndpoint.com", bidderUnruly.URI) -} - -func TestBuildRequest(t *testing.T) { - request := openrtb2.BidRequest{} - expectedJson, _ := json.Marshal(request) - mockHeaders := http.Header{} - mockHeaders.Add("Content-Type", "application/json;charset=utf-8") - mockHeaders.Add("Accept", "application/json") - mockHeaders.Add("X-Unruly-Origin", "Prebid-Server") - data := adapters.RequestData{ - Method: "POST", - Uri: "http://mockEndpoint.com", - Body: expectedJson, - Headers: mockHeaders, - } - - adapter := UnrulyAdapter{URI: "http://mockEndpoint.com"} - - actual, _ := adapter.BuildRequest(&request) - expected := data - if !reflect.DeepEqual(expected, *actual) { - t.Errorf("actual = %v expected = %v", actual, expected) - } - -} - -func TestReplaceImp(t *testing.T) { - imp1 := openrtb2.Imp{ID: "imp1"} - imp2 := openrtb2.Imp{ID: "imp2"} - imp3 := openrtb2.Imp{ID: "imp3"} - newImp := openrtb2.Imp{ID: "imp4"} - request := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}} - adapter := UnrulyAdapter{URI: "http://mockEndpoint.com"} - newRequest := adapter.ReplaceImp(newImp, &request) - - if len(newRequest.Imp) != 1 { - t.Errorf("Size of Imp Array should be 1") - } - if !reflect.DeepEqual(request, openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}}) { - t.Errorf("actual = %v expected = %v", request, openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}}) - } - if !reflect.DeepEqual(newImp, newRequest.Imp[0]) { - t.Errorf("actual = %v expected = %v", newRequest.Imp[0], newImp) - } -} - -func TestConvertBidderNameInExt(t *testing.T) { - imp := openrtb2.Imp{Ext: json.RawMessage(`{"bidder": {"uuid": "1234", "siteid": "aSiteID"}}`)} - - actualImp, err := convertBidderNameInExt(&imp) - - if err != nil { - t.Errorf("actual = %v expected = %v", err, nil) - } - - var unrulyExt ImpExtUnruly - err = json.Unmarshal(actualImp.Ext, &unrulyExt) - - if err != nil { - t.Errorf("actual = %v expected = %v", err, nil) - } - - if unrulyExt.Unruly.UUID != "1234" { - t.Errorf("actual = %v expected = %v", unrulyExt.Unruly.UUID, "1234") - } - - if unrulyExt.Unruly.SiteID != "aSiteID" { - t.Errorf("actual = %v expected = %v", unrulyExt.Unruly.SiteID, "aSiteID") - } -} - -func TestMakeRequests(t *testing.T) { - adapter := UnrulyAdapter{URI: "http://mockEndpoint.com"} - - imp1 := openrtb2.Imp{ID: "imp1", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid1", "siteid": "siteID1"}}`)} - imp2 := openrtb2.Imp{ID: "imp2", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid2", "siteid": "siteID2"}}`)} - imp3 := openrtb2.Imp{ID: "imp3", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid3", "siteid": "siteID3"}}`)} - - expectImp1 := openrtb2.Imp{ID: "imp1", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid1", "siteid": "siteID1"}}`)} - expectImp2 := openrtb2.Imp{ID: "imp2", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid2", "siteid": "siteID2"}}`)} - expectImp3 := openrtb2.Imp{ID: "imp3", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid3", "siteid": "siteID3"}}`)} - - expectImps := []openrtb2.Imp{expectImp1, expectImp2, expectImp3} - - inputRequest := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}} - actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) - mockHeaders := http.Header{} - mockHeaders.Add("Content-Type", "application/json;charset=utf-8") - mockHeaders.Add("Accept", "application/json") - mockHeaders.Add("X-Unruly-Origin", "Prebid-Server") - if len(actualAdapterRequests) != 3 { - t.Errorf("should have 3 imps") - } - for n, imp := range expectImps { - request := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp}} - expectedJson, _ := json.Marshal(request) - data := adapters.RequestData{ - Method: "POST", - Uri: "http://mockEndpoint.com", - Body: expectedJson, - Headers: mockHeaders, - } - if !reflect.DeepEqual(data, *actualAdapterRequests[n]) { - t.Errorf("actual = %v expected = %v", *actualAdapterRequests[0], data) - } - } -} - -func TestGetMediaTypeForImpIsVideo(t *testing.T) { - testID := string("4321") - testBidMediaType := openrtb_ext.BidTypeVideo - imp := openrtb2.Imp{ - ID: testID, - Video: &openrtb2.Video{}, - } - imps := []openrtb2.Imp{imp} - actual, _ := getMediaTypeForImpWithId(testID, imps) - - if actual != "video" { - t.Errorf("actual = %v expected = %v", actual, testBidMediaType) - } -} - -func TestGetMediaTypeForImpWithNoIDPresent(t *testing.T) { - imp := openrtb2.Imp{ - ID: "4321", - Video: &openrtb2.Video{}, - } - imps := []openrtb2.Imp{imp} - _, err := getMediaTypeForImpWithId("1234", imps) - expected := &errortypes.BadInput{ - Message: fmt.Sprintf("Failed to find impression \"%s\" ", "1234"), - } - if !reflect.DeepEqual(expected, err) { - t.Errorf("actual = %v expected = %v", expected, err) - } -} - -func TestConvertToAdapterBidResponseHasCorrectNumberOfBids(t *testing.T) { - imp := openrtb2.Imp{ - ID: "1234", - Video: &openrtb2.Video{}, - } - imp2 := openrtb2.Imp{ - ID: "1235", - Video: &openrtb2.Video{}, - } - - mockResponse := adapters.ResponseData{StatusCode: 200, - Body: json.RawMessage(`{"seatbid":[{"bid":[{"impid":"1234"}]},{"bid":[{"impid":"1235"}]}]}`)} - internalRequest := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp, imp2}} - mockBidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - - typedBid := &adapters.TypedBid{ - Bid: &openrtb2.Bid{ImpID: "1234"}, - BidType: "Video", - } - typedBid2 := &adapters.TypedBid{ - Bid: &openrtb2.Bid{ImpID: "1235"}, - BidType: "Video", - } - - mockBidResponse.Bids = append(mockBidResponse.Bids, typedBid) - mockBidResponse.Bids = append(mockBidResponse.Bids, typedBid2) - - actual, _ := convertToAdapterBidResponse(&mockResponse, &internalRequest) - if !reflect.DeepEqual(*actual.Bids[0].Bid, *mockBidResponse.Bids[0].Bid) { - t.Errorf("actual = %v expected = %v", *actual.Bids[0].Bid, *mockBidResponse.Bids[0].Bid) - } - if !reflect.DeepEqual(*actual.Bids[1].Bid, *mockBidResponse.Bids[1].Bid) { - t.Errorf("actual = %v expected = %v", *actual.Bids[1].Bid, *mockBidResponse.Bids[1].Bid) - } -} diff --git a/adapters/unruly/unrulytest/exemplary/banner-and-video-app.json b/adapters/unruly/unrulytest/exemplary/banner-and-video-app.json new file mode 100644 index 00000000000..ca46f124104 --- /dev/null +++ b/adapters/unruly/unrulytest/exemplary/banner-and-video-app.json @@ -0,0 +1,201 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 1, + "maxduration": 2, + "protocols": [1, 2, 5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1, 2, 3, 4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ], + "app": { + "id": "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", + "name": "Yahoo Weather", + "bundle": "12345", + "storeurl": "https://itunes.apple.com/id628677149", + "cat": ["IAB15", "IAB15-10"], + "ver": "1.0.2", + "publisher": { + "id": "1" + } + }, + "device": { + "dnt": 0, + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit / 534.46(KHTML, like Gecko) Version / 5.1 Mobile / 9 A334 Safari / 7534.48 .3", + "ip": "123.145.167.189", + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11", + "carrier": "VERIZON", + "language": "en", + "make": "Apple", + "model": "iPhone", + "os": "iOS", + "osv": "6.1", + "js": 1, + "connectiontype": 3, + "devicetype": 1 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 1, + "maxduration": 2, + "protocols": [1, 2, 5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1, 2, 3, 4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ], + "app": { + "id": "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", + "name": "Yahoo Weather", + "bundle": "12345", + "storeurl": "https://itunes.apple.com/id628677149", + "cat": ["IAB15", "IAB15-10"], + "ver": "1.0.2", + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit / 534.46(KHTML, like Gecko) Version / 5.1 Mobile / 9 A334 Safari / 7534.48 .3", + "ip": "123.145.167.189", + "devicetype": 1, + "make": "Apple", + "model": "iPhone", + "os": "iOS", + "osv": "6.1", + "js": 1, + "dnt": 0, + "language": "en", + "carrier": "VERIZON", + "connectiontype": 3, + "ifa": "AA000DFE74168477C70D291f574D344790E0BB11" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/unruly/unrulytest/exemplary/banner-and-video-gdpr.json b/adapters/unruly/unrulytest/exemplary/banner-and-video-gdpr.json new file mode 100644 index 00000000000..71fb52edae6 --- /dev/null +++ b/adapters/unruly/unrulytest/exemplary/banner-and-video-gdpr.json @@ -0,0 +1,172 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 1, + "maxduration": 2, + "protocols": [1, 2, 5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1, 2, 3, 4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ], + "user": { + "id": "eyJ0ZW1wVUlEcyI6eyJhZGZvcm0iOnsidWlkIjoiMzA5MTMwOTUxNjQ5NDA1MjcxIiwiZXhwaXJlcyI6IjIwMTgtMDYtMjBUMTE6NDA6MzUuODAwNTE0NzQ3KzA1OjMwIn0sImFkbnhzIjp7InVpZCI6IjM1MTUzMjg2MTAyNjMxNjQ0ODQiLCJleHBpcmVzIjoiMjAxOC0wNi0xOFQxODoxMjoxNy4wMTExMzg2MDgrMDU6MzAifX0sImJkYXkiOiIyMDE4LTA2LTA0VDE4OjEyOjE3LjAxMTEzMDg3NSswNTozMCJ9", + "ext": { + "consent": "BOPVK28OPVK28ABABAENA8-AAAADkCNQCGoQAAQ" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 1, + "maxduration": 2, + "protocols": [1, 2, 5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1, 2, 3, 4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ], + "user": { + "id": "eyJ0ZW1wVUlEcyI6eyJhZGZvcm0iOnsidWlkIjoiMzA5MTMwOTUxNjQ5NDA1MjcxIiwiZXhwaXJlcyI6IjIwMTgtMDYtMjBUMTE6NDA6MzUuODAwNTE0NzQ3KzA1OjMwIn0sImFkbnhzIjp7InVpZCI6IjM1MTUzMjg2MTAyNjMxNjQ0ODQiLCJleHBpcmVzIjoiMjAxOC0wNi0xOFQxODoxMjoxNy4wMTExMzg2MDgrMDU6MzAifX0sImJkYXkiOiIyMDE4LTA2LTA0VDE4OjEyOjE3LjAxMTEzMDg3NSswNTozMCJ9", + "ext": { + "consent": "BOPVK28OPVK28ABABAENA8-AAAADkCNQCGoQAAQ" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/unruly/unrulytest/exemplary/banner-and-video-site.json b/adapters/unruly/unrulytest/exemplary/banner-and-video-site.json new file mode 100644 index 00000000000..9a136c6e9a1 --- /dev/null +++ b/adapters/unruly/unrulytest/exemplary/banner-and-video-site.json @@ -0,0 +1,180 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 1, + "maxduration": 2, + "protocols": [1, 2, 5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1, 2, 3, 4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ], + "site": { + "id": "102855", + "cat": ["IAB3-1"], + "domain": "www.foobar.com", + "page": "http://www.foobar.com/1234.html ", + "publisher": { + "id": "8953", + "name": "foobar.com", + "cat": ["IAB3-1"], + "domain": "foobar.com" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version / 5.1 .7 Safari / 534.57 .2", + "ip": "123.145.167.10" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 1, + "maxduration": 2, + "protocols": [1, 2, 5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1, 2, 3, 4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ], + "site": { + "id": "102855", + "cat": ["IAB3-1"], + "domain": "www.foobar.com", + "page": "http://www.foobar.com/1234.html ", + "publisher": { + "id": "8953", + "name": "foobar.com", + "cat": ["IAB3-1"], + "domain": "foobar.com" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version / 5.1 .7 Safari / 534.57 .2", + "ip": "123.145.167.10" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["yahoo.com"], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/unruly/unrulytest/exemplary/banner-and-video.json b/adapters/unruly/unrulytest/exemplary/banner-and-video.json new file mode 100644 index 00000000000..c29b2d28f09 --- /dev/null +++ b/adapters/unruly/unrulytest/exemplary/banner-and-video.json @@ -0,0 +1,181 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2, + 5 + ], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ], + "delivery": [ + 1 + ], + "api": [ + 1, + 2, + 3, + 4 + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + }, + { + "id": "test-imp-video-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 2, + 5 + ], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ], + "delivery": [ + 1 + ], + "api": [ + 1, + 2, + 3, + 4 + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + }] + } + ] +} \ No newline at end of file diff --git a/adapters/unruly/unrulytest/exemplary/simple-banner.json b/adapters/unruly/unrulytest/exemplary/simple-banner.json new file mode 100644 index 00000000000..56bc41e23fa --- /dev/null +++ b/adapters/unruly/unrulytest/exemplary/simple-banner.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "Unruly", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + } + ] +} diff --git a/adapters/unruly/unrulytest/exemplary/simple-video.json b/adapters/unruly/unrulytest/exemplary/simple-video.json new file mode 100644 index 00000000000..c3a89a56ee3 --- /dev/null +++ b/adapters/unruly/unrulytest/exemplary/simple-video.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "Unruly", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.500000, + "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/unruly/unrulytest/exemplary/video.json b/adapters/unruly/unrulytest/exemplary/video.json deleted file mode 100644 index ba1598dd1c8..00000000000 --- a/adapters/unruly/unrulytest/exemplary/video.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "mockBidRequest": { - "id": "someID", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": [ - "video/mp4" - ] - }, - "tagid": "0420420421", - "secure": 0, - "ext": { - "bidder": { - "uuid": "someUUID", - "siteid": "someSiteId" - }, - "ozone": { - "adUnitCode": "mpu", - "transactionId": "9aaf1ba9-d5ed-4f19-99d5-3216bff5903b" - }, - "prebid": { - "storedrequest": { - "id": "0420420421" - } - } - } - } - ] - }, - - "HttpCalls": [ - { - "expectedRequest": { - "id": "someID", - "body": {"id":"someID","imp":[{"id":"test-imp-id","video":{"mimes":["video/mp4"]},"tagid":"0420420421","secure":0,"ext":{"unruly":{"uuid":"someUUID","siteid":"someSiteId"}}}]}, - "uri": "http://targeting.unrulymedia.com/openrtb/2.2", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": [ - "video/mp4" - ] - }, - "tagid": "0420420421", - "secure": 0, - "ext": { - "bidder": { - "uuid": "someUUID", - "siteid": "someSiteId" - }, - "ozone": { - "adUnitCode": "mpu", - "transactionId": "9aaf1ba9-d5ed-4f19-99d5-3216bff5903b" - }, - "prebid": { - "storedrequest": { - "id": "0420420421" - } - } - } - } - ] - }, - - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "cur": "USD", - "bids": [ - { - "seat": "unruly", - "bid": [{ - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.500000, - "adm": "some-test-ad", - "crid": "crid_10", - "w": 1024, - "h": 576 - }] - } - ] - } - } - } - ], - - "expectedBidResponses": [ - { - "id":"8815f346-9e64-4fb8-a4e1-11e03b4b71aa", - "body": { - "id": "test-request-id", - "cur": "USD", - "bids": [ - { - "seat": "unruly", - "bid": [{ - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id", - "price": 0.500000, - "adm": "some-test-ad", - "crid": "crid_10", - "w": 1024, - "h": 576 - }] - } - ] - } - } - - ] -} diff --git a/adapters/unruly/unrulytest/supplemental/missing-extension.json b/adapters/unruly/unrulytest/supplemental/missing-extension.json new file mode 100644 index 00000000000..d313a1759d4 --- /dev/null +++ b/adapters/unruly/unrulytest/supplemental/missing-extension.json @@ -0,0 +1,34 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-missing-ext-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "maxextended": 30, + "minbitrate": 300, + "maxbitrate": 1500, + "protocols": [1,2,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "ext data not provided in imp id=test-missing-ext-id. Abort all Request", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/unruly/unrulytest/supplemental/no-matching-impid.json b/adapters/unruly/unrulytest/supplemental/no-matching-impid.json new file mode 100644 index 00000000000..63c04dc5516 --- /dev/null +++ b/adapters/unruly/unrulytest/supplemental/no-matching-impid.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "Unruly", + "bid": [ + { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "sdf", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "crid_10", + "w": 1024, + "h": 576 + } + ] + } + ] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "comparison": "literal", + "value": "Bid response imp ID sdf not found in bid request containing imps [test-imp-id]\n" + } + ] +} diff --git a/adapters/unruly/unrulytest/supplemental/status-code-204.json b/adapters/unruly/unrulytest/supplemental/status-code-204.json new file mode 100644 index 00000000000..e569f726519 --- /dev/null +++ b/adapters/unruly/unrulytest/supplemental/status-code-204.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/unruly/unrulytest/supplemental/status-code-400.json b/adapters/unruly/unrulytest/supplemental/status-code-400.json new file mode 100644 index 00000000000..6df35add8f7 --- /dev/null +++ b/adapters/unruly/unrulytest/supplemental/status-code-400.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [ + 1, + 3, + 5 + ], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [ + 2 + ], + "delivery": [ + 1 + ], + "api": [ + 1, + 2, + 3, + 4 + ] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "comparison": "literal", + "value": "unexpected status code: 400. Run with request.debug = 1 for more info" + } + ] +} diff --git a/adapters/unruly/unrulytest/supplemental/status-code-401.json b/adapters/unruly/unrulytest/supplemental/status-code-401.json new file mode 100644 index 00000000000..77dbb110dd5 --- /dev/null +++ b/adapters/unruly/unrulytest/supplemental/status-code-401.json @@ -0,0 +1,74 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://targeting.unrulymedia.com/unruly_prebid_server", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "siteid": 72721 + } + } + } + ] + } + }, + "mockResponse": { + "status": 401 + } + } + ], + "expectedMakeBidsErrors": [ + { + "comparison": "literal", + "value": "unexpected status code: 401. Run with request.debug = 1 for more info" + } + ] +} diff --git a/config/config.go b/config/config.go index ce7d85fe160..652183706bd 100644 --- a/config/config.go +++ b/config/config.go @@ -867,7 +867,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.trustx.endpoint", "https://grid.bidswitch.net/sp_bid?sp=trustx") v.SetDefault("adapters.ucfunnel.endpoint", "https://pbs.aralego.com/prebid") v.SetDefault("adapters.unicorn.endpoint", "https://ds.uncn.jp/pb/0/bid.json") - v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2") + v.SetDefault("adapters.unruly.endpoint", "https://targeting.unrulymedia.com/unruly_prebid_server") v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") v.SetDefault("adapters.verizonmedia.disabled", true) v.SetDefault("adapters.viewdeos.endpoint", "http://ghb.sync.viewdeos.com/pbs/ortb") diff --git a/openrtb_ext/imp_unruly.go b/openrtb_ext/imp_unruly.go index 574cb59d671..4fe87e9e46c 100644 --- a/openrtb_ext/imp_unruly.go +++ b/openrtb_ext/imp_unruly.go @@ -1,6 +1,5 @@ package openrtb_ext type ExtImpUnruly struct { - UUID string `json:"uuid"` - SiteID string `json:"siteid"` + SiteID int `json:"siteid"` } diff --git a/static/bidder-info/unruly.yaml b/static/bidder-info/unruly.yaml index ebefcb51c7b..7e018a0a4b1 100644 --- a/static/bidder-info/unruly.yaml +++ b/static/bidder-info/unruly.yaml @@ -1,12 +1,14 @@ maintainer: - email: "adspaces@unrulygroup.com" -gvlVendorID: 162 + email: "prebidsupport@unrulygroup.com" +gvlVendorID: 36 capabilities: - site: + app: mediaTypes: + - banner - video - app: + site: mediaTypes: + - banner - video userSync: iframe: diff --git a/static/bidder-params/unruly.json b/static/bidder-params/unruly.json index da293f1d478..e3c8a744895 100644 --- a/static/bidder-params/unruly.json +++ b/static/bidder-params/unruly.json @@ -4,14 +4,10 @@ "description": "A schema which validates params accepted by the Unruly adapter", "type": "object", "properties": { - "uuid": { - "type": "string", - "description": "uuid" - }, "siteid": { - "type": "string", + "type": "integer", "description": "ID for publisher site" } }, - "required": ["uuid", "siteid"] + "required": ["siteid"] } From a9fed18c9b773488df25331927b101dcb4129c28 Mon Sep 17 00:00:00 2001 From: vladi-mmg Date: Mon, 4 Oct 2021 19:02:08 +0300 Subject: [PATCH 105/140] Marsmedia: Change bid params from zone to zoneId (#2005) Co-authored-by: Vladi Izgayev --- adapters/marsmedia/marsmedia.go | 4 ++-- .../marsmediatest/exemplary/simple-banner.json | 4 ++-- .../marsmediatest/exemplary/simple-video.json | 4 ++-- .../marsmediatest/exemplary/valid-extension.json | 4 ++-- .../marsmediatest/supplemental/invalid-extension.json | 2 +- .../marsmediatest/supplemental/invalid-imp.json | 2 +- .../marsmediatest/supplemental/missing-param.json | 4 ++-- adapters/marsmedia/params_test.go | 10 +++------- openrtb_ext/imp_marsmedia.go | 2 +- static/bidder-params/marsmedia.json | 5 ++--- 10 files changed, 18 insertions(+), 23 deletions(-) diff --git a/adapters/marsmedia/marsmedia.go b/adapters/marsmedia/marsmedia.go index a63db09e208..0f4fad5a953 100644 --- a/adapters/marsmedia/marsmedia.go +++ b/adapters/marsmedia/marsmedia.go @@ -37,13 +37,13 @@ func (a *MarsmediaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo var marsmediaExt openrtb_ext.ExtImpMarsmedia if err := json.Unmarshal(bidderExt.Bidder, &marsmediaExt); err != nil { return nil, []error{&errortypes.BadInput{ - Message: "ext.bidder.zone not provided", + Message: "ext.bidder.zoneId not provided", }} } if marsmediaExt.ZoneID == "" { return nil, []error{&errortypes.BadInput{ - Message: "Zone is empty", + Message: "zoneId is empty", }} } diff --git a/adapters/marsmedia/marsmediatest/exemplary/simple-banner.json b/adapters/marsmedia/marsmediatest/exemplary/simple-banner.json index aa3981a0042..f13f4aadfa2 100644 --- a/adapters/marsmedia/marsmediatest/exemplary/simple-banner.json +++ b/adapters/marsmedia/marsmediatest/exemplary/simple-banner.json @@ -15,7 +15,7 @@ }, "ext": { "bidder": { - "zone": "9999" + "zoneId": "9999" } } } @@ -44,7 +44,7 @@ }, "ext": { "bidder": { - "zone": "9999" + "zoneId": "9999" } } } diff --git a/adapters/marsmedia/marsmediatest/exemplary/simple-video.json b/adapters/marsmedia/marsmediatest/exemplary/simple-video.json index 59b48c043eb..8582a97a1d7 100644 --- a/adapters/marsmedia/marsmediatest/exemplary/simple-video.json +++ b/adapters/marsmedia/marsmediatest/exemplary/simple-video.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "zone": "9999" + "zoneId": "9999" } } } @@ -37,7 +37,7 @@ }, "ext": { "bidder": { - "zone": "9999" + "zoneId": "9999" } } } diff --git a/adapters/marsmedia/marsmediatest/exemplary/valid-extension.json b/adapters/marsmedia/marsmediatest/exemplary/valid-extension.json index f93755d946a..ddd310a6156 100644 --- a/adapters/marsmedia/marsmediatest/exemplary/valid-extension.json +++ b/adapters/marsmedia/marsmediatest/exemplary/valid-extension.json @@ -12,7 +12,7 @@ }, "ext":{ "bidder":{ - "zone": "9999" + "zoneId": "9999" } } } @@ -37,7 +37,7 @@ }, "ext": { "bidder": { - "zone": "9999" + "zoneId": "9999" } } } diff --git a/adapters/marsmedia/marsmediatest/supplemental/invalid-extension.json b/adapters/marsmedia/marsmediatest/supplemental/invalid-extension.json index 4ee31fe4cbf..12c844f6a0a 100644 --- a/adapters/marsmedia/marsmediatest/supplemental/invalid-extension.json +++ b/adapters/marsmedia/marsmediatest/supplemental/invalid-extension.json @@ -24,7 +24,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "ext.bidder.zone not provided", + "value": "ext.bidder.zoneId not provided", "comparison": "literal" } ] diff --git a/adapters/marsmedia/marsmediatest/supplemental/invalid-imp.json b/adapters/marsmedia/marsmediatest/supplemental/invalid-imp.json index 641066d719f..8a851ab77a0 100644 --- a/adapters/marsmedia/marsmediatest/supplemental/invalid-imp.json +++ b/adapters/marsmedia/marsmediatest/supplemental/invalid-imp.json @@ -3,7 +3,7 @@ "id": "test-request-id", "ext": { "bidder": { - "zone": "9999" + "zoneId": "9999" } } }, diff --git a/adapters/marsmedia/marsmediatest/supplemental/missing-param.json b/adapters/marsmedia/marsmediatest/supplemental/missing-param.json index 70117f3bcd0..b88fba5a8a1 100644 --- a/adapters/marsmedia/marsmediatest/supplemental/missing-param.json +++ b/adapters/marsmedia/marsmediatest/supplemental/missing-param.json @@ -18,7 +18,7 @@ }, "ext": { "bidder": { - "zone":"" + "zoneId":"" } } @@ -27,7 +27,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "Zone is empty", + "value": "zoneId is empty", "comparison": "literal" } ] diff --git a/adapters/marsmedia/params_test.go b/adapters/marsmedia/params_test.go index 43cd49c2ed3..78acd72e437 100644 --- a/adapters/marsmedia/params_test.go +++ b/adapters/marsmedia/params_test.go @@ -40,20 +40,16 @@ func TestInvalidParams(t *testing.T) { } var validParams = []string{ - `{"zone": "9999"}`, + `{"zoneId": "9999"}`, } var invalidParams = []string{ - `{"zone": 100}`, - `{"headerbidding": false}`, - `{"zone": true}`, - `{"zoneId": 123, "headerbidding": true}`, - `{"zoneID": "1"}`, + `{"zoneId": 100}`, + `{"zoneId": true}`, ``, `null`, `true`, `9`, `1.2`, `[]`, - `{}`, } diff --git a/openrtb_ext/imp_marsmedia.go b/openrtb_ext/imp_marsmedia.go index 39bc433d661..c27fe2db3de 100644 --- a/openrtb_ext/imp_marsmedia.go +++ b/openrtb_ext/imp_marsmedia.go @@ -2,5 +2,5 @@ package openrtb_ext // ExtImpMarsmedia defines the contract for bidrequest.imp[i].ext.marsmedia type ExtImpMarsmedia struct { - ZoneID string `json:"zone"` + ZoneID string `json:"zoneId"` } diff --git a/static/bidder-params/marsmedia.json b/static/bidder-params/marsmedia.json index 208a42e8474..b812eb89a2a 100644 --- a/static/bidder-params/marsmedia.json +++ b/static/bidder-params/marsmedia.json @@ -4,10 +4,9 @@ "description": "A schema which validates params accepted by the Marsmedia adapter", "type": "object", "properties": { - "zone": { + "zoneId": { "type": "string", "description": "Zone ID to use." } - }, - "required": ["zone"] + } } From 078ce00aa89c207d892ca9a13521c0775448d368 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 5 Oct 2021 23:20:40 +0200 Subject: [PATCH 106/140] New Adapter: Impactify (#2004) Co-authored-by: Thomas De Stefano --- adapters/impactify/impactify.go | 183 ++++++++++++++++++ adapters/impactify/impactify_test.go | 20 ++ .../exemplary/sample_banner.json | 99 ++++++++++ .../impactifytest/exemplary/sample_video.json | 95 +++++++++ .../supplemental/bad_response.json | 64 ++++++ .../impactifytest/supplemental/buyeruid.json | 129 ++++++++++++ .../impactifytest/supplemental/headers.json | 120 ++++++++++++ .../supplemental/headers_ip.json | 120 ++++++++++++ .../impactifytest/supplemental/http_204.json | 68 +++++++ .../impactifytest/supplemental/http_400.json | 93 +++++++++ .../impactifytest/supplemental/http_500.json | 93 +++++++++ .../supplemental/invalid_imp.json | 18 ++ .../supplemental/negative_price.json | 159 +++++++++++++++ .../supplemental/no_seat_bid.json | 68 +++++++ .../not_supported_media_type.json | 65 +++++++ .../impactifytest/supplemental/referer.json | 138 +++++++++++++ .../supplemental/unable_decode_ext.json | 29 +++ adapters/impactify/params_test.go | 49 +++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_impactify.go | 8 + static/bidder-info/impactify.yaml | 13 ++ static/bidder-params/impactify.json | 25 +++ 24 files changed, 1661 insertions(+) create mode 100644 adapters/impactify/impactify.go create mode 100644 adapters/impactify/impactify_test.go create mode 100644 adapters/impactify/impactifytest/exemplary/sample_banner.json create mode 100644 adapters/impactify/impactifytest/exemplary/sample_video.json create mode 100644 adapters/impactify/impactifytest/supplemental/bad_response.json create mode 100644 adapters/impactify/impactifytest/supplemental/buyeruid.json create mode 100644 adapters/impactify/impactifytest/supplemental/headers.json create mode 100644 adapters/impactify/impactifytest/supplemental/headers_ip.json create mode 100644 adapters/impactify/impactifytest/supplemental/http_204.json create mode 100644 adapters/impactify/impactifytest/supplemental/http_400.json create mode 100644 adapters/impactify/impactifytest/supplemental/http_500.json create mode 100644 adapters/impactify/impactifytest/supplemental/invalid_imp.json create mode 100644 adapters/impactify/impactifytest/supplemental/negative_price.json create mode 100644 adapters/impactify/impactifytest/supplemental/no_seat_bid.json create mode 100644 adapters/impactify/impactifytest/supplemental/not_supported_media_type.json create mode 100644 adapters/impactify/impactifytest/supplemental/referer.json create mode 100644 adapters/impactify/impactifytest/supplemental/unable_decode_ext.json create mode 100644 adapters/impactify/params_test.go create mode 100644 openrtb_ext/imp_impactify.go create mode 100644 static/bidder-info/impactify.yaml create mode 100644 static/bidder-params/impactify.json diff --git a/adapters/impactify/impactify.go b/adapters/impactify/impactify.go new file mode 100644 index 00000000000..5d64be0351c --- /dev/null +++ b/adapters/impactify/impactify.go @@ -0,0 +1,183 @@ +package impactify + +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" + "strings" +) + +type adapter struct { + endpoint string +} + +type ImpactifyExtBidder struct { + Impactify openrtb_ext.ExtImpImpactify `json:"impactify"` +} + +type DefaultExtBidder struct { + Bidder openrtb_ext.ExtImpImpactify `json:"bidder"` +} + +func (a *adapter) MakeRequests(bidRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var adapterRequests []*adapters.RequestData + + for i := 0; i < len(bidRequest.Imp); i++ { + // Check if imp comes with bid floor amount defined in a foreign currency + if bidRequest.Imp[i].BidFloor > 0 && bidRequest.Imp[i].BidFloorCur != "" && strings.ToUpper(bidRequest.Imp[i].BidFloorCur) != "USD" { + // Convert to US dollars + convertedValue, err := reqInfo.ConvertCurrency(bidRequest.Imp[i].BidFloor, bidRequest.Imp[i].BidFloorCur, "USD") + if err != nil { + return nil, []error{err} + } + bidRequest.Imp[i].BidFloorCur = "USD" + bidRequest.Imp[i].BidFloor = convertedValue + } + + // Set the CUR of bid to USD after converting all floors + bidRequest.Cur = []string{"USD"} + + var impactifyExt ImpactifyExtBidder + + var defaultExt DefaultExtBidder + err := json.Unmarshal(bidRequest.Imp[i].Ext, &defaultExt) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unable to decode the imp ext : \"%s\"", bidRequest.Imp[i].ID), + }} + } + + impactifyExt.Impactify = defaultExt.Bidder + bidRequest.Imp[i].Ext, err = json.Marshal(impactifyExt) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unable to decode the imp ext : \"%s\"", bidRequest.Imp[i].ID), + }} + } + } + + if len(bidRequest.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No valid impressions in the bid request", + }} + } + + reqJSON, err := json.Marshal(bidRequest) + if err != nil { + return nil, []error{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") + if bidRequest.Device != nil { + if bidRequest.Device.UA != "" { + headers.Add("User-Agent", bidRequest.Device.UA) + } + // Add IPv4 or IPv6 if available + if bidRequest.Device.IP != "" { + headers.Add("X-Forwarded-For", bidRequest.Device.IP) + } else if bidRequest.Device.IPv6 != "" { + headers.Add("X-Forwarded-For", bidRequest.Device.IPv6) + } + } + if bidRequest.Site != nil { + headers.Add("Referer", bidRequest.Site.Page) + } + + // set user's cookie + if bidRequest.User != nil && bidRequest.User.BuyerUID != "" { + headers.Add("Cookie", "uids="+bidRequest.User.BuyerUID) + } + + adapterRequests = append(adapterRequests, &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: reqJSON, + Headers: headers, + }) + + return adapterRequests, 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 + } else if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: "Invalid request.", + }} + } else if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected HTTP status %d.", response.StatusCode), + }} + } + + var openRtbBidResponse openrtb2.BidResponse + + if err := json.Unmarshal(response.Body, &openRtbBidResponse); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad server body response", + }} + } + + if len(openRtbBidResponse.SeatBid) == 0 { + return nil, nil + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(openRtbBidResponse.SeatBid[0].Bid)) + bidResponse.Currency = openRtbBidResponse.Cur + + sb := openRtbBidResponse.SeatBid[0] + for i := 0; i < len(sb.Bid); i++ { + if !(sb.Bid[i].Price > 0) { + continue + } + + impMediaType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + return nil, []error{err} + } + + bid := sb.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: impMediaType, + }) + } + return bidResponse, nil +} + +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 + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find a supported media type impression \"%s\"", impID), + } +} + +// Builder builds a new instance of the Impactify 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/impactify/impactify_test.go b/adapters/impactify/impactify_test.go new file mode 100644 index 00000000000..a01a94b0322 --- /dev/null +++ b/adapters/impactify/impactify_test.go @@ -0,0 +1,20 @@ +package impactify + +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.BidderImpactify, config.Adapter{ + Endpoint: "https://sonic.impactify.media/bidder"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "impactifytest", bidder) +} diff --git a/adapters/impactify/impactifytest/exemplary/sample_banner.json b/adapters/impactify/impactifytest/exemplary/sample_banner.json new file mode 100644 index 00000000000..f5d76c9e4c7 --- /dev/null +++ b/adapters/impactify/impactifytest/exemplary/sample_banner.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 3, + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "test": 3, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "impactify", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }, + "type": "banner" + }] + } + ] +} diff --git a/adapters/impactify/impactifytest/exemplary/sample_video.json b/adapters/impactify/impactifytest/exemplary/sample_video.json new file mode 100644 index 00000000000..4c904833ad8 --- /dev/null +++ b/adapters/impactify/impactifytest/exemplary/sample_video.json @@ -0,0 +1,95 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 3, + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "test": 3, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "impactify", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/bad_response.json b/adapters/impactify/impactifytest/supplemental/bad_response.json new file mode 100644 index 00000000000..a9dfa4cc3a2 --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/bad_response.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "cur": ["USD"], + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "{\"id\"data.lost" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad server body response", + "comparison": "literal" + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/buyeruid.json b/adapters/impactify/impactifytest/supplemental/buyeruid.json new file mode 100644 index 00000000000..df940813e91 --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/buyeruid.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 3, + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ], + "user": { + "buyeruid": "TESTBUYERUID" + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" + ], + "X-Forwarded-For": [ + "81.92.224.65" + ], + "Cookie": [ + "uids=TESTBUYERUID" + ] + }, + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "test": 3, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + }, + "user": { + "buyeruid": "TESTBUYERUID" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "impactify", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/headers.json b/adapters/impactify/impactifytest/supplemental/headers.json new file mode 100644 index 00000000000..8d4fe2fabfd --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/headers.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 3, + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" + ], + "X-Forwarded-For": [ + "81.92.224.65" + ] + }, + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "test": 3, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "impactify", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/headers_ip.json b/adapters/impactify/impactifytest/supplemental/headers_ip.json new file mode 100644 index 00000000000..1add398ad7e --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/headers_ip.json @@ -0,0 +1,120 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 3, + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ipv6": "2001:DB8:3333:4::5" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" + ], + "X-Forwarded-For": [ + "2001:DB8:3333:4::5" + ] + }, + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "test": 3, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ipv6": "2001:DB8:3333:4::5" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "impactify", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/http_204.json b/adapters/impactify/impactifytest/supplemental/http_204.json new file mode 100644 index 00000000000..bca38ba06ac --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/http_204.json @@ -0,0 +1,68 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": { + "id": "test-request-id", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [] + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/http_400.json b/adapters/impactify/impactifytest/supplemental/http_400.json new file mode 100644 index 00000000000..10d1f56c206 --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/http_400.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 3, + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" + ], + "X-Forwarded-For": [ + "81.92.224.65" + ] + }, + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "test": 3, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Invalid request.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/impactify/impactifytest/supplemental/http_500.json b/adapters/impactify/impactifytest/supplemental/http_500.json new file mode 100644 index 00000000000..7633cc0aae3 --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/http_500.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 3, + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" + ], + "X-Forwarded-For": [ + "81.92.224.65" + ] + }, + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "test": 3, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected HTTP status 500.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/impactify/impactifytest/supplemental/invalid_imp.json b/adapters/impactify/impactifytest/supplemental/invalid_imp.json new file mode 100644 index 00000000000..11ca86c7f0b --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/invalid_imp.json @@ -0,0 +1,18 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "ext": { + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "No valid impressions in the bid request", + "comparison": "literal" + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/negative_price.json b/adapters/impactify/impactifytest/supplemental/negative_price.json new file mode 100644 index 00000000000..02827baa984 --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/negative_price.json @@ -0,0 +1,159 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 3, + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" + ], + "X-Forwarded-For": [ + "81.92.224.65" + ] + }, + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "test": 3, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "4e97a64f-e57f-48a7-8770-d5bcc95bb988", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": -1, + "adm": "", + "adid": "230769549", + "adomain": [ + "https://event.webcasts.com/starthere.jsp?ei=1268923&tp_key=84ed77e3e5" + ], + "iurl": "https://ams1-ib.adnxs.com/cr?id=230769549", + "cid": "1212", + "crid": "230769549", + "cat": [ + "IAB7-25" + ], + "w": 1, + "h": 1, + "ext": { + "bidder": { + "bidder": { + "impactify": { + "auction_id": 9001143799525811000, + "bid_ad_type": 1, + "bidder_id": 101, + "brand_id": 840665, + "creative_info": { + "video": { + "duration": 15, + "mimes": [ + "video/mp4", + "video/webm", + "video/3gpp" + ] + } + } + } + }, + "prebid": { + "type": "video", + "video": { + "duration": 15, + "primary_category": "" + } + } + }, + "prebid": { + "type": "video" + } + } + } + ], + "seat": "impactify" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "impactify": 17 + }, + "prebid": { + "auctiontimestamp": 1632899733548 + } + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ] +} \ No newline at end of file diff --git a/adapters/impactify/impactifytest/supplemental/no_seat_bid.json b/adapters/impactify/impactifytest/supplemental/no_seat_bid.json new file mode 100644 index 00000000000..e1371c3ad20 --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/no_seat_bid.json @@ -0,0 +1,68 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [] + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/not_supported_media_type.json b/adapters/impactify/impactifytest/supplemental/not_supported_media_type.json new file mode 100644 index 00000000000..e47594eea1c --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/not_supported_media_type.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "audio": {}, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://sonic.impactify.media/bidder", + "body": { + "cur": [ + "USD" + ], + "id": "test-request-id", + "imp": [{ + "ext":{ + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + }, + "id": "test-imp-id", + "audio": { + "mimes": null + } + }] + } + }, + "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 a supported media type impression \"test-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/referer.json b/adapters/impactify/impactifytest/supplemental/referer.json new file mode 100644 index 00000000000..457f5cfbd77 --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/referer.json @@ -0,0 +1,138 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "test": 3, + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ], + "site": { + "page": "https://impactify.io" + }, + "user": { + "buyeruid": "TESTBUYERUID" + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" + ], + "X-Forwarded-For": [ + "81.92.224.65" + ], + "Referer": [ + "https://impactify.io" + ], + "Cookie": [ + "uids=TESTBUYERUID" + ] + }, + "uri": "https://sonic.impactify.media/bidder", + "body": { + "id": "test-request-id", + "at":1, + "cur": ["USD"], + "test": 3, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + "ip": "81.92.224.65" + }, + "site": { + "page": "https://impactify.io" + }, + "user": { + "buyeruid": "TESTBUYERUID" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "impactify": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "impactify", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 18, + "adm": "", + "crid": "crid_10", + "w": 1, + "h": 1 + }, + "type": "video" + }] + } + ] +} diff --git a/adapters/impactify/impactifytest/supplemental/unable_decode_ext.json b/adapters/impactify/impactifytest/supplemental/unable_decode_ext.json new file mode 100644 index 00000000000..f6d4a2614fc --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/unable_decode_ext.json @@ -0,0 +1,29 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": 123 + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Unable to decode the imp ext : \"test-imp-id\"", + "comparison": "literal" + } + ] +} diff --git a/adapters/impactify/params_test.go b/adapters/impactify/params_test.go new file mode 100644 index 00000000000..845ea29f00b --- /dev/null +++ b/adapters/impactify/params_test.go @@ -0,0 +1,49 @@ +package impactify + +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.BidderImpactify, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Impactify 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.BidderImpactify, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"appId": "impactify.io", "format": "screen", "style": "inline"}`, + `{"appId": "impactify.io", "format": "screen", "style": "impact"}`, +} + +var invalidParams = []string{ + `{"appId":"impactify.io"}`, + `{"appId":"impactify.io", "format": "screen"}`, + ``, + `null`, + `true`, + `[]`, + `{}`, +} diff --git a/config/config.go b/config/config.go index 652183706bd..b0de4965587 100644 --- a/config/config.go +++ b/config/config.go @@ -807,6 +807,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") v.SetDefault("adapters.huaweiads.endpoint", "https://acd.op.hicloud.com/ppsadx/getResult") v.SetDefault("adapters.huaweiads.disabled", true) + v.SetDefault("adapters.impactify.endpoint", "https://sonic.impactify.media/bidder") 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://prebid-server.ioadx.com/bidRequest/?partnerId={{.AccountID}}") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index c796df06547..48b68eac50a 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -61,6 +61,7 @@ import ( "github.com/prebid/prebid-server/adapters/grid" "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/huaweiads" + "github.com/prebid/prebid-server/adapters/impactify" "github.com/prebid/prebid-server/adapters/improvedigital" "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/interactiveoffers" @@ -193,6 +194,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderGrid: grid.Builder, openrtb_ext.BidderGumGum: gumgum.Builder, openrtb_ext.BidderHuaweiAds: huaweiads.Builder, + openrtb_ext.BidderImpactify: impactify.Builder, openrtb_ext.BidderImprovedigital: improvedigital.Builder, openrtb_ext.BidderInMobi: inmobi.Builder, openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 69ed1f42413..c95d0fb8142 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -132,6 +132,7 @@ const ( BidderGrid BidderName = "grid" BidderGumGum BidderName = "gumgum" BidderHuaweiAds BidderName = "huaweiads" + BidderImpactify BidderName = "impactify" BidderImprovedigital BidderName = "improvedigital" BidderInMobi BidderName = "inmobi" BidderInteractiveoffers BidderName = "interactiveoffers" @@ -266,6 +267,7 @@ func CoreBidderNames() []BidderName { BidderGrid, BidderGumGum, BidderHuaweiAds, + BidderImpactify, BidderImprovedigital, BidderInMobi, BidderInteractiveoffers, diff --git a/openrtb_ext/imp_impactify.go b/openrtb_ext/imp_impactify.go new file mode 100644 index 00000000000..122a98a4110 --- /dev/null +++ b/openrtb_ext/imp_impactify.go @@ -0,0 +1,8 @@ +package openrtb_ext + +// ExtImpImpactify defines the contract for bidrequest.imp[i].ext.impactify +type ExtImpImpactify struct { + AppID string `json:"appId"` + Format string `json:"format"` + Style string `json:"style"` +} diff --git a/static/bidder-info/impactify.yaml b/static/bidder-info/impactify.yaml new file mode 100644 index 00000000000..b0316d7acae --- /dev/null +++ b/static/bidder-info/impactify.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "contact@impactify.io" +gvlVendorID: 606 +capabilities: + site: + mediaTypes: + - banner + - video +userSync: + iframe: + url: "https://sonic.impactify.media/static/cookie_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect_url={{.RedirectURL}}" + userMacro: "{IMPACTIFY_UID}" + diff --git a/static/bidder-params/impactify.json b/static/bidder-params/impactify.json new file mode 100644 index 00000000000..a8ecfb4f739 --- /dev/null +++ b/static/bidder-params/impactify.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Impactify Adapter Params", + "description": "A schema which validates params accepted by the Impactify adapter", + "type": "object", + "properties": { + "appId": { + "type": "string", + "description": "The appId of your website", + "minLength": 1 + }, + "format": { + "type": "string", + "description": "The format of the ad", + "minLength": 1 + }, + "style": { + "type": "string", + "description": "The style of the ad", + "minLength": 1 + } + }, + + "required": ["appId", "format", "style"] +} From dc940a0d63101b314c6a57a239ba29acc93f67c8 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 7 Oct 2021 13:34:45 -0400 Subject: [PATCH 107/140] Disable Legacy /auction Endpoint By Default (#2032) --- config/config.go | 2 +- config/config_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index b0de4965587..966e24e6fc8 100644 --- a/config/config.go +++ b/config/config.go @@ -953,7 +953,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("auto_gen_source_tid", true) v.SetDefault("generate_bid_id", false) v.SetDefault("generate_request_id", false) - v.SetDefault("enable_legacy_auction", true) + v.SetDefault("enable_legacy_auction", false) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") diff --git a/config/config_test.go b/config/config_test.go index ac7f4a2d747..819eb21f819 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -144,7 +144,7 @@ func TestDefaults(t *testing.T) { 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) - cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, true) + cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, false) //Assert purpose VendorExceptionMap hash tables were built correctly expectedTCF2 := TCF2{ @@ -354,7 +354,7 @@ request_validation: ipv4_private_networks: ["1.1.1.0/24"] ipv6_private_networks: ["1111::/16", "2222::/16"] generate_bid_id: true -enable_legacy_auction: false +enable_legacy_auction: true `) var adapterExtraInfoConfig = []byte(` @@ -579,7 +579,7 @@ func TestFullConfig(t *testing.T) { 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, "") - cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, false) + cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, true) } func TestUnmarshalAdapterExtraInfo(t *testing.T) { From 34d8da3d81e60d369ea364e56f3d2d4049316d8e Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 7 Oct 2021 13:39:12 -0400 Subject: [PATCH 108/140] Add PBS Logo (#2034) --- static/pbs-logo.svg | 142 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 static/pbs-logo.svg diff --git a/static/pbs-logo.svg b/static/pbs-logo.svg new file mode 100644 index 00000000000..925a611f7b2 --- /dev/null +++ b/static/pbs-logo.svg @@ -0,0 +1,142 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From af99d6932e7a49e0a044d17a363b2ef2851cf74a Mon Sep 17 00:00:00 2001 From: Mikhail Ivanchenko Date: Wed, 13 Oct 2021 17:30:37 +0300 Subject: [PATCH 109/140] New Adapter: NextMillennium (#2016) --- adapters/nextmillennium/nextmillennium.go | 150 ++++++++++++++++++ .../nextmillennium/nextmillennium_test.go | 20 +++ .../nextmillenniumtest/exemplary/banner.json | 91 +++++++++++ .../supplemental/bad-response.json | 58 +++++++ .../supplemental/error-response.json | 52 ++++++ .../nextmillenniumtest/supplemental/ext.json | 33 ++++ .../supplemental/no-content.json | 46 ++++++ adapters/nextmillennium/params_test.go | 47 ++++++ config/config.go | 1 + exchange/adapter_builders.go | 6 +- openrtb_ext/bidders.go | 2 + openrtb_ext/imp_nextmillennium.go | 5 + static/bidder-info/nextmillennium.yaml | 9 ++ static/bidder-params/nextmillennium.json | 14 ++ 14 files changed, 532 insertions(+), 2 deletions(-) create mode 100644 adapters/nextmillennium/nextmillennium.go create mode 100644 adapters/nextmillennium/nextmillennium_test.go create mode 100644 adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json create mode 100644 adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json create mode 100644 adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json create mode 100644 adapters/nextmillennium/nextmillenniumtest/supplemental/ext.json create mode 100644 adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json create mode 100644 adapters/nextmillennium/params_test.go create mode 100644 openrtb_ext/imp_nextmillennium.go create mode 100644 static/bidder-info/nextmillennium.yaml create mode 100644 static/bidder-params/nextmillennium.json diff --git a/adapters/nextmillennium/nextmillennium.go b/adapters/nextmillennium/nextmillennium.go new file mode 100644 index 00000000000..9171b21c714 --- /dev/null +++ b/adapters/nextmillennium/nextmillennium.go @@ -0,0 +1,150 @@ +package nextmillennium + +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 +} + +type NextMillenniumBidRequest struct { + ID string `json:"id"` + Test uint8 `json:"test,omitempty"` + Ext struct { + Prebid struct { + StoredRequest struct { + ID string `json:"id"` + } `json:"storedrequest"` + } `json:"prebid"` + } `json:"ext"` +} + +//MakeRequests prepares request information for prebid-server core +func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + resImps, err := getImpressionsInfo(request.Imp) + if len(err) > 0 { + return nil, err + } + + result := make([]*adapters.RequestData, 0, len(resImps)) + for _, imp := range resImps { + bidRequest, err := adapter.buildAdapterRequest(request, imp) + if err != nil { + return nil, []error{err} + } + result = append(result, bidRequest) + } + + return result, nil +} + +func getImpressionsInfo(imps []openrtb2.Imp) (resImps []*openrtb_ext.ImpExtNextMillennium, errors []error) { + for _, imp := range imps { + impExt, err := getImpressionExt(&imp) + if err != nil { + errors = append(errors, err) + continue + } + + resImps = append(resImps, impExt) + } + + return +} + +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ImpExtNextMillennium, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + var nextMillenniumExt openrtb_ext.ImpExtNextMillennium + if err := json.Unmarshal(bidderExt.Bidder, &nextMillenniumExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + + return &nextMillenniumExt, nil +} + +func (adapter *adapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ImpExtNextMillennium) (*adapters.RequestData, error) { + newBidRequest := createBidRequest(prebidBidRequest, params) + + reqJSON, err := json.Marshal(newBidRequest) + if err != nil { + return nil, 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") + + return &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint, + Body: reqJSON, + Headers: headers}, nil +} + +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ImpExtNextMillennium) *NextMillenniumBidRequest { + bidRequest := NextMillenniumBidRequest{ + ID: prebidBidRequest.ID, + Test: uint8(prebidBidRequest.Test), + } + bidRequest.Ext.Prebid.StoredRequest.ID = params.PlacementID + + return &bidRequest +} + +//MakeBids translates NextMillennium bid response to prebid-server specific format +func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var msg = "" + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + if response.StatusCode != http.StatusOK { + msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + + } + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + msg = fmt.Sprintf("Bad server response: %d", err) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + if len(bidResp.SeatBid) != 1 { + var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + + seatBid := bidResp.SeatBid[0] + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + + for i := 0; i < len(seatBid.Bid); i++ { + bid := seatBid.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: openrtb_ext.BidTypeBanner, + }) + } + return bidResponse, nil +} + +// Builder builds a new instance of the NextMillennium adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + return &adapter{ + endpoint: config.Endpoint, + }, nil +} diff --git a/adapters/nextmillennium/nextmillennium_test.go b/adapters/nextmillennium/nextmillennium_test.go new file mode 100644 index 00000000000..c5573bb3272 --- /dev/null +++ b/adapters/nextmillennium/nextmillennium_test.go @@ -0,0 +1,20 @@ +package nextmillennium + +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.BidderNextMillennium, config.Adapter{ + Endpoint: "https://pbs.nextmillmedia.com/openrtb2/auction"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "nextmillenniumtest", bidder) +} diff --git a/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json new file mode 100644 index 00000000000..ff6386d3c89 --- /dev/null +++ b/adapters/nextmillennium/nextmillenniumtest/exemplary/banner.json @@ -0,0 +1,91 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "placement_id": "7819" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.nextmillmedia.com/openrtb2/auction", + "body":{ + "id": "testid", + "ext": { + "prebid": { + "storedrequest": { + "id": "7819" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "f7b3d2da-e762-410c-b069-424f92c4c4b2", + "seatbid": [ + { + "bid": [ + { + "impid": "123654", + "id": "7457329903666272789", + "price": 0.5, + "adm": "Hello! It\"s a test ad!", + "adid": "96846035", + "adomain": ["test.addomain.com"], + "w": 300, + "h": 250 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7457329903666272789", + "impid": "123654", + "price": 0.5, + "adm": "Hello! It\"s a test ad!", + "adid": "96846035", + "adomain": ["test.addomain.com"], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json new file mode 100644 index 00000000000..64de88abc40 --- /dev/null +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json @@ -0,0 +1,58 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "placement_id": "7819" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.nextmillmedia.com/openrtb2/auction", + "body": { + "id": "testid", + "ext": { + "prebid": { + "storedrequest": { + "id": "7819" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [] + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid SeatBids count: 0", + "comparison": "literal" + } + ] +} diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json new file mode 100644 index 00000000000..1fa4b195f28 --- /dev/null +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/error-response.json @@ -0,0 +1,52 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "placement_id": "7819" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.nextmillmedia.com/openrtb2/auction", + "body": { + "id": "testid", + "ext": { + "prebid": { + "storedrequest": { + "id": "7819" + } + } + } + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 500", + "comparison": "literal" + } + ] +} diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/ext.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/ext.json new file mode 100644 index 00000000000..58fefc6349f --- /dev/null +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/ext.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "placement_id": "1265" + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json new file mode 100644 index 00000000000..a12897a4230 --- /dev/null +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/no-content.json @@ -0,0 +1,46 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "placement_id": "7819" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pbs.nextmillmedia.com/openrtb2/auction", + "body": { + "id": "testid", + "ext": { + "prebid": { + "storedrequest": { + "id": "7819" + } + } + } + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/nextmillennium/params_test.go b/adapters/nextmillennium/params_test.go new file mode 100644 index 00000000000..286d80c611b --- /dev/null +++ b/adapters/nextmillennium/params_test.go @@ -0,0 +1,47 @@ +package nextmillennium + +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.BidderNextMillennium, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected NextMillennium 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.BidderNextMillennium, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placement_id": "123654"}`, +} + +var invalidParams = []string{ + `{"placement_id": 123654}`, + `nil`, + ``, + `[]`, + `true`, + `{}`, +} diff --git a/config/config.go b/config/config.go index 966e24e6fc8..597ac1b41a7 100644 --- a/config/config.go +++ b/config/config.go @@ -830,6 +830,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.mobilefuse.endpoint", "http://mfx.mobilefuse.com/openrtb?pub_id={{.PublisherID}}") v.SetDefault("adapters.mobfoxpb.endpoint", "http://bes.mobfox.com/?c=__route__&m=__method__&key=__key__") v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs") + v.SetDefault("adapters.nextmillennium.endpoint", "https://pbs.nextmillmedia.com/openrtb2/auction") v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}") 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}}") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 48b68eac50a..f34e74ea41b 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -51,7 +51,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" + evolution "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" @@ -82,6 +82,7 @@ import ( "github.com/prebid/prebid-server/adapters/mobfoxpb" "github.com/prebid/prebid-server/adapters/mobilefuse" "github.com/prebid/prebid-server/adapters/nanointeractive" + "github.com/prebid/prebid-server/adapters/nextmillennium" "github.com/prebid/prebid-server/adapters/ninthdecimal" "github.com/prebid/prebid-server/adapters/nobid" "github.com/prebid/prebid-server/adapters/onetag" @@ -98,7 +99,7 @@ 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" + salunamedia "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" @@ -217,6 +218,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder, openrtb_ext.BidderMobileFuse: mobilefuse.Builder, openrtb_ext.BidderNanoInteractive: nanointeractive.Builder, + openrtb_ext.BidderNextMillennium: nextmillennium.Builder, openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, openrtb_ext.BidderNoBid: nobid.Builder, openrtb_ext.BidderOneTag: onetag.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index c95d0fb8142..7976451877c 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -155,6 +155,7 @@ const ( BidderMobfoxpb BidderName = "mobfoxpb" BidderMobileFuse BidderName = "mobilefuse" BidderNanoInteractive BidderName = "nanointeractive" + BidderNextMillennium BidderName = "nextmillennium" BidderNinthDecimal BidderName = "ninthdecimal" BidderNoBid BidderName = "nobid" BidderOneTag BidderName = "onetag" @@ -290,6 +291,7 @@ func CoreBidderNames() []BidderName { BidderMobfoxpb, BidderMobileFuse, BidderNanoInteractive, + BidderNextMillennium, BidderNinthDecimal, BidderNoBid, BidderOneTag, diff --git a/openrtb_ext/imp_nextmillennium.go b/openrtb_ext/imp_nextmillennium.go new file mode 100644 index 00000000000..dbc970d0cfa --- /dev/null +++ b/openrtb_ext/imp_nextmillennium.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtNextMillennium struct { + PlacementID string `json:"placement_id"` +} diff --git a/static/bidder-info/nextmillennium.yaml b/static/bidder-info/nextmillennium.yaml new file mode 100644 index 00000000000..8caf4ac38d7 --- /dev/null +++ b/static/bidder-info/nextmillennium.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "appdevs@nextmillennium.io" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner diff --git a/static/bidder-params/nextmillennium.json b/static/bidder-params/nextmillennium.json new file mode 100644 index 00000000000..07b869c2b7d --- /dev/null +++ b/static/bidder-params/nextmillennium.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "NextMillennium Adapter Params", + "description": "A schema which validates params accepted by the NextMillennium adapter", + "type": "object", + "properties": { + "placement_id": { + "type": "string", + "minLength": 1, + "description": "An id used to identify NextMillennium placement" + } + }, + "required": ["placement_id"] +} From 45a480ac761519b8d2e3aed232d05bfb7afd2fa4 Mon Sep 17 00:00:00 2001 From: Jim Naumann Date: Wed, 13 Oct 2021 11:34:27 -0400 Subject: [PATCH 110/140] Beachfront bidder - include primary category in response (#2039) Co-authored-by: jim --- adapters/beachfront/beachfront.go | 13 +- .../exemplary/adm-video-app.json | 2 + .../beachfronttest/exemplary/adm-video.json | 2 + .../adm-video-app-alphanum-bundle.json | 2 + .../adm-video-app-malformed-bundle.json | 2 + .../supplemental/adm-video-by-explicit.json | 2 + .../supplemental/adm-video-no-cat.json | 124 ++++++++++++++++++ .../supplemental/adm-video-no-ip.json | 2 + .../supplemental/adm-video-schain.json | 2 + ...-four-variations-on-nothing-adm-video.json | 10 +- .../bidfloor-test-ext-wins-adm-video.json | 2 + .../bidfloor-test-imp-wins-adm-video.json | 2 + .../supplemental/six-nine-combo.json | 3 + 13 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-no-cat.json diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 7f4658d442c..8af61eb9a5a 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -537,10 +537,15 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb2.BidRequest, exter for i := 0; i < len(bids); i++ { - // If we unmarshal without an error, this is an AdM video - if err := json.Unmarshal(bids[i].Ext, &dur); err == nil { - var impVideo openrtb_ext.ExtBidPrebidVideo - impVideo.Duration = int(dur.Duration) + if err := json.Unmarshal(bids[i].Ext, &dur); err == nil && dur.Duration > 0 { + + impVideo := openrtb_ext.ExtBidPrebidVideo{ + Duration: int(dur.Duration), + } + + if len(bids[i].Cat) > 0 { + impVideo.PrimaryCategory = bids[i].Cat[0] + } bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bids[i], diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json b/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json index 0734a212c61..e20201bb35d 100644 --- a/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video-app.json @@ -79,6 +79,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -110,6 +111,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video.json b/adapters/beachfront/beachfronttest/exemplary/adm-video.json index edde1301dcc..404c35adeeb 100644 --- a/adapters/beachfront/beachfronttest/exemplary/adm-video.json +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video.json @@ -77,6 +77,7 @@ "adomain": [ "beachfront.io" ], + "cat":["IAB2"], "cid": "277", "crid": "532", "w": 300, @@ -110,6 +111,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json index 0e3a32d5437..943a4583ae9 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json @@ -78,6 +78,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -109,6 +110,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json index 8327426ca0f..9d0ca24ab8b 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json @@ -79,6 +79,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -110,6 +111,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json index 8c05d65a3b1..25b4a1c4456 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json @@ -80,6 +80,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -110,6 +111,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-no-cat.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-cat.json new file mode 100644 index 00000000000..edde1301dcc --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-cat.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json index 842e0f3777e..d2cc2bc9ad3 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json @@ -76,6 +76,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -106,6 +107,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-schain.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-schain.json index 329298176bb..f249da4722e 100644 --- a/adapters/beachfront/beachfronttest/supplemental/adm-video-schain.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-schain.json @@ -113,6 +113,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -144,6 +145,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-four-variations-on-nothing-adm-video.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-four-variations-on-nothing-adm-video.json index ad63cbac59b..147efb78332 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidfloor-four-variations-on-nothing-adm-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/bidfloor-four-variations-on-nothing-adm-video.json @@ -54,7 +54,7 @@ "bidfloor": 0.01, "ext": { "bidder": { - "bidfloor": 0.01, + "bidfloor": 10.01, "appId": "videoAppId1" } }, @@ -126,6 +126,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -187,6 +188,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -248,6 +250,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -309,6 +312,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -341,6 +345,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -365,6 +370,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -389,6 +395,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -413,6 +420,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-ext-wins-adm-video.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-ext-wins-adm-video.json index 6b898901496..eed3da992d6 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-ext-wins-adm-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-ext-wins-adm-video.json @@ -79,6 +79,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -110,6 +111,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-imp-wins-adm-video.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-imp-wins-adm-video.json index 8ebb6967b32..edc63f2f5ee 100644 --- a/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-imp-wins-adm-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/bidfloor-test-imp-wins-adm-video.json @@ -80,6 +80,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { @@ -111,6 +112,7 @@ ], "cid": "277", "crid": "532", + "cat":["IAB2"], "w": 300, "h": 250, "ext": { diff --git a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json index c2a52be6a25..1779248edfc 100644 --- a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json +++ b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json @@ -290,6 +290,7 @@ { "id": "crid_7", "impid": "ADMVideoWithBannerImp1", + "cat":["IAB2"], "price": 0.8, "adm": "http://example.com/vast.xml", "w": 300, @@ -349,6 +350,7 @@ "id": "crid_7", "impid": "ADMVideoWithBannerImp2", "price": 0.8, + "cat":["IAB2"], "adm": "http://example.com/vast.xml", "w": 300, "h": 400, @@ -407,6 +409,7 @@ "id": "crid_8", "impid": "ADMVideoImp6", "price": 0.8, + "cat":["IAB2"], "adm": "http://example.com/vast.xml", "w": 100, "h": 150, From fa1743c3eda4353318bf369a4463ec3db940f799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rok=20Su=C5=A1nik?= Date: Thu, 14 Oct 2021 15:41:53 +0200 Subject: [PATCH 111/140] add test for eids on outbrain (#2041) --- .../outbraintest/supplemental/eids.json | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 adapters/outbrain/outbraintest/supplemental/eids.json diff --git a/adapters/outbrain/outbraintest/supplemental/eids.json b/adapters/outbrain/outbraintest/supplemental/eids.json new file mode 100644 index 00000000000..cb917fe75c9 --- /dev/null +++ b/adapters/outbrain/outbraintest/supplemental/eids.json @@ -0,0 +1,155 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "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 + }, + "user": { + "ext": { + "eids": [{ + "source": "liveramp.com", + "uids": [{ + "id": "id-value", + "atype": 3 + }] + }] + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "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 + }, + "user": { + "ext": { + "eids": [{ + "source": "liveramp.com", + "uids": [{ + "id": "id-value", + "atype": 3 + }] + }] + } + } + } + }, + "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" + } + ] + } + ] +} From 8c19d5dd44a761f0e5ff71d59185e4375844ab13 Mon Sep 17 00:00:00 2001 From: Mansi Nahar Date: Thu, 14 Oct 2021 16:58:51 -0400 Subject: [PATCH 112/140] =?UTF-8?q?Fix=20flaky=20cookie=20trim=20test=20de?= =?UTF-8?q?pending=20on=20time.Now()=20and=20the=20uuid=20order=E2=80=A6?= =?UTF-8?q?=20(#2038)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- usersync/cookie_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index f3944a389c3..4792db1969f 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -341,7 +341,7 @@ func TestTrimCookiesClosestExpirationDates(t *testing.T) { cookieToSend := &Cookie{ uids: map[string]uidWithExpiry{ "k1": newTempId("12345678901234567890123456789012345678901234567890", 7), - "k2": newTempId("abcdefghijklmnopqrstuvwxyz", 6), + "k2": newTempId("abcdefghijklmnopqrstuvwxyz", 1), "k3": newTempId("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 6), "k4": newTempId("12345678901234567890123456789612345678901234567890", 5), "k5": newTempId("aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ", 4), @@ -359,7 +359,7 @@ func TestTrimCookiesClosestExpirationDates(t *testing.T) { testCases := []aTest{ {maxCookieSize: 2000, expKeys: []string{"k1", "k2", "k3", "k4", "k5", "k6", "k7"}}, //1 don't trim, set {maxCookieSize: 0, expKeys: []string{"k1", "k2", "k3", "k4", "k5", "k6", "k7"}}, //2 unlimited size: don't trim, set - {maxCookieSize: 800, expKeys: []string{"k1", "k2", "k3", "k4"}}, //3 trim to size and set + {maxCookieSize: 800, expKeys: []string{"k1", "k5", "k4", "k3"}}, //3 trim to size and set {maxCookieSize: 500, expKeys: []string{"k1", "k3"}}, //4 trim to size and set {maxCookieSize: 200, expKeys: []string{}}, //5 insufficient size, trim to zero length and set {maxCookieSize: -100, expKeys: []string{}}, //6 invalid size, trim to zero length and set @@ -449,7 +449,7 @@ func ensureConsistency(t *testing.T, cookie *Cookie) { func newTempId(uid string, offset int) uidWithExpiry { return uidWithExpiry{ UID: uid, - Expires: time.Now().Add(time.Duration(offset) * time.Minute), + Expires: time.Now().Add(time.Duration(offset) * time.Minute).UTC(), } } From fe211a9010bc264e43bfa7a396a3078c19de5ebf Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 18 Oct 2021 13:19:28 -0400 Subject: [PATCH 113/140] Remove /auction Endpoint (#2033) --- adapters/adform/adform.go | 173 +--- adapters/adform/adform_test.go | 226 +---- adapters/adhese/adhese.go | 2 +- adapters/adman/adman.go | 4 - adapters/appnexus/appnexus.go | 230 +---- adapters/appnexus/appnexus_test.go | 421 +-------- adapters/connectad/connectad.go | 4 - adapters/consumable/instant.go | 2 +- adapters/consumable/utils.go | 20 - adapters/conversant/cnvr_legacy.go | 291 ------ adapters/conversant/cnvr_legacy_test.go | 853 ------------------ adapters/deepintent/deepintent.go | 4 - adapters/dmx/dmx_test.go | 4 - adapters/infoawarebidder_test.go | 1 - adapters/ix/ix.go | 248 +---- adapters/ix/ix_test.go | 703 +-------------- adapters/legacy.go | 97 -- adapters/openrtb_util.go | 174 ---- adapters/openrtb_util_test.go | 543 ----------- adapters/pubmatic/pubmatic.go | 315 +------ adapters/pubmatic/pubmatic_test.go | 673 +------------- adapters/pulsepoint/pulsepoint.go | 201 +---- adapters/pulsepoint/pulsepoint_test.go | 294 +----- adapters/rubicon/rubicon.go | 335 +------ adapters/rubicon/rubicon_test.go | 658 +------------- adapters/sharethrough/utils_test.go | 4 - adapters/sonobi/sonobi.go | 10 - adapters/sovrn/sovrn.go | 167 +--- adapters/sovrn/sovrn_test.go | 278 +----- analytics/filesystem/file_module.go | 2 - analytics/pubstack/pubstack_module_test.go | 44 - cache/dummycache/dummycache.go | 65 -- cache/dummycache/dummycache_test.go | 31 - cache/filecache/filecache.go | 123 --- cache/filecache/filecache_test.go | 79 -- cache/legacy.go | 33 - cache/postgrescache/postgrescache.go | 139 --- cache/postgrescache/postgrescache_test.go | 94 -- config/config.go | 17 - config/config_test.go | 13 - config/stored_requests.go | 1 - config/structlog.go | 4 +- endpoints/auction.go | 513 ----------- endpoints/auction_test.go | 654 -------------- endpoints/openrtb2/auction.go | 9 - endpoints/openrtb2/auction_test.go | 16 - endpoints/openrtb2/video_auction_test.go | 16 - exchange/auction.go | 7 - exchange/auction_test.go | 6 - exchange/bidder_test.go | 1 - exchange/exchange_test.go | 2 +- exchange/targeting_test.go | 19 +- go.mod | 3 - go.sum | 4 - main.go | 3 - metrics/config/metrics.go | 11 - metrics/config/metrics_test.go | 2 - metrics/go_metrics.go | 10 - metrics/go_metrics_test.go | 4 - metrics/metrics.go | 1 - metrics/metrics_mock.go | 5 - metrics/prometheus/prometheus.go | 4 - metrics/prometheus/prometheus_test.go | 22 - metrics/prometheus/type_conversion.go | 9 - openrtb_ext/bidders.go | 2 - openrtb_ext/imp_beachfront.go | 6 +- openrtb_ext/imp_nanointeractive.go | 8 +- pbs/pbsrequest.go | 403 --------- pbs/pbsrequest_test.go | 735 --------------- pbs/pbsresponse.go | 84 -- pbs/pbsresponse_test.go | 88 -- pbs/usersync.go | 26 +- prebid_cache_client/client.go | 2 +- prebid_cache_client/client_test.go | 12 +- prebid_cache_client/prebid_cache.go | 122 --- prebid_cache_client/prebid_cache_test.go | 150 --- privacy/ccpa/parsedpolicy_test.go | 11 - router/router.go | 94 +- router/router_test.go | 45 - server/prometheus.go | 5 +- .../backends/file_fetcher/fetcher_test.go | 2 +- .../backends/http_fetcher/fetcher.go | 1 - .../backends/http_fetcher/fetcher_test.go | 38 - stored_requests/caches/nil_cache/nil_cache.go | 2 - stored_requests/config/config.go | 14 +- stored_requests/events/events.go | 2 +- usersync/cookie_test.go | 2 +- 87 files changed, 107 insertions(+), 10648 deletions(-) delete mode 100644 adapters/consumable/utils.go delete mode 100644 adapters/conversant/cnvr_legacy.go delete mode 100644 adapters/conversant/cnvr_legacy_test.go delete mode 100644 adapters/legacy.go delete mode 100644 adapters/openrtb_util.go delete mode 100644 adapters/openrtb_util_test.go delete mode 100644 cache/dummycache/dummycache.go delete mode 100644 cache/dummycache/dummycache_test.go delete mode 100644 cache/filecache/filecache.go delete mode 100644 cache/filecache/filecache_test.go delete mode 100644 cache/legacy.go delete mode 100644 cache/postgrescache/postgrescache.go delete mode 100644 cache/postgrescache/postgrescache_test.go delete mode 100644 endpoints/auction.go delete mode 100644 endpoints/auction_test.go delete mode 100644 pbs/pbsrequest.go delete mode 100644 pbs/pbsrequest_test.go delete mode 100644 pbs/pbsresponse.go delete mode 100644 pbs/pbsresponse_test.go delete mode 100644 prebid_cache_client/prebid_cache.go delete mode 100644 prebid_cache_client/prebid_cache_test.go diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index 225c7af35d4..a432bb075b3 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -2,31 +2,27 @@ package adform import ( "bytes" - "context" "encoding/base64" "encoding/json" "errors" "fmt" - "io/ioutil" "net/http" "net/url" "strconv" "strings" - "github.com/buger/jsonparser" - "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" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const version = "0.1.3" type AdformAdapter struct { - http *adapters.HTTPAdapter URL *url.URL version string } @@ -100,155 +96,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -// used for cookies and such -func (a *AdformAdapter) Name() string { - return "adform" -} - -func (a *AdformAdapter) SkipNoCookies() bool { - return false -} - -func (a *AdformAdapter) Call(ctx context.Context, request *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - adformRequest, err := pbsRequestToAdformRequest(a, request, bidder) - if err != nil { - return nil, err - } - - uri := adformRequest.buildAdformUrl(a) - - debug := &pbs.BidderDebug{RequestURI: uri} - if request.IsDebug { - bidder.Debug = append(bidder.Debug, debug) - } - - httpRequest, err := http.NewRequest("GET", uri, nil) - if err != nil { - return nil, err - } - - httpRequest.Header = adformRequest.buildAdformHeaders(a) - - response, err := ctxhttp.Do(ctx, a.http.Client, httpRequest) - if err != nil { - return nil, err - } - - debug.StatusCode = response.StatusCode - - if response.StatusCode == 204 { - return nil, nil - } - - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - responseBody := string(body) - - if response.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", response.StatusCode, responseBody), - } - } - - if response.StatusCode != 200 { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", response.StatusCode, responseBody), - } - } - - if request.IsDebug { - debug.ResponseBody = responseBody - } - - adformBids, err := parseAdformBids(body) - if err != nil { - return nil, err - } - - bids := toPBSBidSlice(adformBids, adformRequest) - - return bids, nil -} - -func pbsRequestToAdformRequest(a *AdformAdapter, request *pbs.PBSRequest, bidder *pbs.PBSBidder) (*adformRequest, error) { - adUnits := make([]*adformAdUnit, 0, len(bidder.AdUnits)) - for _, adUnit := range bidder.AdUnits { - var adformAdUnit adformAdUnit - if err := json.Unmarshal(adUnit.Params, &adformAdUnit); err != nil { - return nil, err - } - mid, err := adformAdUnit.MasterTagId.Int64() - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - if mid <= 0 { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("master tag(placement) id is invalid=%s", adformAdUnit.MasterTagId), - } - } - adformAdUnit.bidId = adUnit.BidID - adformAdUnit.adUnitCode = adUnit.Code - adUnits = append(adUnits, &adformAdUnit) - } - - userId, _, _ := request.Cookie.GetUID(a.Name()) - - gdprApplies := request.ParseGDPR() - if gdprApplies != "0" && gdprApplies != "1" { - gdprApplies = "" - } - consent := request.ParseConsent() - - return &adformRequest{ - adUnits: adUnits, - ip: request.Device.IP, - advertisingId: request.Device.IFA, - userAgent: request.Device.UA, - bidderCode: bidder.BidderCode, - isSecure: request.Secure == 1, - referer: request.Url, - userId: userId, - tid: request.Tid, - gdprApplies: gdprApplies, - consent: consent, - currency: defaultCurrency, - }, nil -} - -func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice { - bids := make(pbs.PBSBidSlice, 0) - - for i, bid := range adformBids { - adm, bidType := getAdAndType(bid) - if adm == "" { - continue - } - pbsBid := pbs.PBSBid{ - BidID: r.adUnits[i].bidId, - AdUnitCode: r.adUnits[i].adUnitCode, - BidderCode: r.bidderCode, - Price: bid.Price, - Adm: adm, - Width: int64(bid.Width), - Height: int64(bid.Height), - DealId: bid.DealId, - Creative_id: bid.CreativeId, - CreativeMediaType: string(bidType), - } - - bids = append(bids, &pbsBid) - } - - return bids -} - -// COMMON - func (r *adformRequest) buildAdformUrl(a *AdformAdapter) string { parameters := url.Values{} @@ -359,20 +206,6 @@ func parseAdformBids(response []byte) ([]*adformBid, error) { // BIDDER Interface -func NewAdformLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpointURL string) *AdformAdapter { - var uriObj *url.URL - uriObj, err := url.Parse(endpointURL) - if err != nil { - panic(fmt.Sprintf("Incorrect Adform request url %s, check the configuration, please.", endpointURL)) - } - - return &AdformAdapter{ - http: adapters.NewHTTPAdapter(httpConfig), - URL: uriObj, - version: version, - } -} - func (a *AdformAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { adformRequest, errors := openRtbToAdformRequest(request) if len(adformRequest.adUnits) == 0 { diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index 53219f4c4c0..53a658f9715 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -2,26 +2,18 @@ package adform import ( "bytes" - "context" "encoding/json" + "fmt" "net/http" - "net/http/httptest" "strconv" "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters/adapterstest" - "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/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -71,31 +63,6 @@ type aBidInfo struct { buyerUID string secure bool currency string - delay time.Duration -} - -var adformTestData aBidInfo - -// Legacy auction tests - -func DummyAdformServer(w http.ResponseWriter, r *http.Request) { - errorString := assertAdformServerRequest(adformTestData, r, false) - if errorString != nil { - http.Error(w, *errorString, http.StatusInternalServerError) - return - } - - if adformTestData.delay > 0 { - <-time.After(adformTestData.delay) - } - - adformServerResponse, err := createAdformServerResponse(adformTestData) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(adformServerResponse) } func createAdformServerResponse(testData aBidInfo) ([]byte, error) { @@ -136,168 +103,6 @@ func createAdformServerResponse(testData aBidInfo) ([]byte, error) { return adformServerResponse, err } -func TestAdformBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyAdformServer)) - defer server.Close() - - adapter, ctx, prebidRequest := initTestData(server, t) - - bids, err := adapter.Call(ctx, prebidRequest, prebidRequest.Bidders[0]) - - if err != nil { - t.Fatalf("Should not have gotten adapter error: %v", err) - } - if len(bids) != 3 { - t.Fatalf("Received %d bids instead of 3", len(bids)) - } - expectedTypes := []openrtb_ext.BidType{ - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeVideo, - } - - for i, bid := range bids { - - if bid.CreativeMediaType != string(expectedTypes[i]) { - t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], bid.CreativeMediaType) - } - - matched := false - for _, tag := range adformTestData.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.BidderCode != "adform" { - t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode) - } - if bid.Price != tag.price { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) - } - if bid.Width != int64(adformTestData.width) || bid.Height != int64(adformTestData.height) { - t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, adformTestData.width, adformTestData.height) - } - if bid.Adm != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - } - if bid.DealId != tag.dealId { - t.Errorf("Incorrect deal id '%s' expected '%s'", bid.DealId, tag.dealId) - } - if bid.Creative_id != tag.creativeId { - t.Errorf("Incorrect creative id '%s' expected '%s'", bid.Creative_id, tag.creativeId) - } - } - } - if !matched { - t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - } - - // same test but with request timing out - adformTestData.delay = 5 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - bids, err = adapter.Call(ctx, prebidRequest, prebidRequest.Bidders[0]) - if err == nil { - t.Fatalf("Should have gotten a timeout error: %v", err) - } -} - -func initTestData(server *httptest.Server, t *testing.T) (*AdformAdapter, context.Context, *pbs.PBSRequest) { - adformTestData = createTestData(false) - - // prepare adapter - conf := *adapters.DefaultHTTPAdapterConfig - adapter := NewAdformLegacyAdapter(&conf, server.URL) - - prebidRequest := preparePrebidRequest(server.URL, t) - ctx := context.TODO() - - return adapter, ctx, prebidRequest -} - -func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { - body := preparePrebidRequestBody(adformTestData, t) - prebidHttpRequest := httptest.NewRequest("POST", serverUrl, body) - prebidHttpRequest.Header.Add("User-Agent", adformTestData.deviceUA) - prebidHttpRequest.Header.Add("Referer", adformTestData.referrer) - prebidHttpRequest.Header.Add("X-Real-IP", adformTestData.deviceIP) - - pbsCookie := usersync.ParseCookieFromRequest(prebidHttpRequest, &config.HostCookie{}) - pbsCookie.TrySync("adform", adformTestData.buyerUID) - fakeWriter := httptest.NewRecorder() - - pbsCookie.SetCookieOnResponse(fakeWriter, false, &config.HostCookie{Domain: ""}, time.Minute) - prebidHttpRequest.Header.Add("Cookie", fakeWriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - r, err := pbs.ParsePBSRequest(prebidHttpRequest, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &config.HostCookie{}) - if err != nil { - t.Fatalf("ParsePBSRequest failed: %v", err) - } - if len(r.Bidders) != 1 { - t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(r.Bidders)) - } - if r.Bidders[0].BidderCode != "adform" { - t.Fatalf("ParsePBSRequest returned invalid bidder") - } - - // can't be set in preparePrebidRequestBody as will be lost during json serialization and deserialization - // for the adapters which don't support OpenRTB requests the old PBSRequest is created from OpenRTB request - // so User and Regs are copied from OpenRTB request, see legacy.go -> toLegacyRequest - regs := getRegs() - r.Regs = ®s - user := openrtb2.User{ - Ext: getUserExt(), - } - r.User = &user - - return r -} - -func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer { - prebidRequest := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 4), - Device: &openrtb2.Device{ - UA: requestData.deviceUA, - IP: requestData.deviceIP, - IFA: requestData.deviceIFA, - }, - Tid: requestData.tid, - Secure: 0, - } - for i, tag := range requestData.tags { - prebidRequest.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - Sizes: []openrtb2.Format{ - { - W: int64(requestData.width), - H: int64(requestData.height), - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "adform", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: json.RawMessage(formatAdUnitJson(tag)), - }, - }, - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(prebidRequest) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - fmt.Println("body", body) - return body -} - -// OpenRTB auction tests - func TestOpenRTBRequest(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ Endpoint: "https://adx.adform.net"}) @@ -324,7 +129,7 @@ func TestOpenRTBRequest(t *testing.T) { } r.Header = httpRequests[0].Headers - errorString := assertAdformServerRequest(testData, r, true) + errorString := assertAdformServerRequest(testData, r) if errorString != nil { t.Errorf("Request error: %s", *errorString) } @@ -499,16 +304,6 @@ func TestOpenRTBSurpriseResponse(t *testing.T) { } } -// Properties tests - -func TestAdformProperties(t *testing.T) { - adapter := NewAdformLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "adx.adform.net/adx") - - if adapter.SkipNoCookies() != false { - t.Fatalf("should have been false") - } -} - // helpers func getRegs() openrtb2.Regs { @@ -588,7 +383,7 @@ func formatAdUnitParam(fieldName string, fieldValue string) string { return "" } -func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb bool) *string { +func assertAdformServerRequest(testData aBidInfo, r *http.Request) *string { if ok, err := equal("GET", r.Method, "HTTP method"); !ok { return err } @@ -598,15 +393,8 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb boo } } - var midsWithCurrency = "" - var queryString = "" - if isOpenRtb { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS" - queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency - } else { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9VVNEJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9VVNE&bWlkPTMyMzQ3JnJjdXI9VVNE" // no way to pass currency in legacy adapter - queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency - } + midsWithCurrency := "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS" + queryString := "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency if ok, err := equal(queryString, r.URL.RawQuery, "Query string"); !ok { return err diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index 6fc12e3df5e..b23b49b3774 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -109,7 +109,7 @@ func (a *AdheseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap // Compose url endpointParams := macros.EndpointTemplateParams{AccountID: params.Account} - host, err := macros.ResolveMacros(*&a.endpointTemplate, endpointParams) + host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) if err != nil { errs = append(errs, WrapReqError("Could not compose url from template and request account val: "+err.Error())) return nil, errs diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index 808951d3aba..fd31bc9d14f 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -25,10 +25,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -type admanParams struct { - TagID string `json:"TagID"` -} - // MakeRequests create bid request for adman demand func (a *AdmanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index b1004601774..3695f541532 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -1,8 +1,6 @@ package appnexus import ( - "bytes" - "context" "encoding/json" "errors" "fmt" @@ -15,9 +13,6 @@ import ( "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - - "golang.org/x/net/context/ctxhttp" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" @@ -27,22 +22,12 @@ import ( const defaultPlatformID int = 5 -type AppNexusAdapter struct { - http *adapters.HTTPAdapter +type adapter struct { URI string iabCategoryMap map[string]string hbSource int } -// used for cookies and such -func (a *AppNexusAdapter) Name() string { - return "adnxs" -} - -func (a *AppNexusAdapter) SkipNoCookies() bool { - return false -} - type KeyVal struct { Key string `json:"key,omitempty"` Values []string `json:"value,omitempty"` @@ -52,21 +37,6 @@ type appnexusAdapterOptions struct { IabCategories map[string]string `json:"iab_categories"` } -type appnexusParams struct { - LegacyPlacementId int `json:"placementId"` - LegacyInvCode string `json:"invCode"` - LegacyTrafficSourceCode string `json:"trafficSourceCode"` - PlacementId int `json:"placement_id"` - InvCode string `json:"inv_code"` - Member string `json:"member"` - Keywords []KeyVal `json:"keywords"` - TrafficSourceCode string `json:"traffic_source_code"` - Reserve float64 `json:"reserve"` - Position string `json:"position"` - UsePmtRule *bool `json:"use_pmt_rule"` - PrivateSizes json.RawMessage `json:"private_sizes"` -} - type appnexusImpExtAppnexus struct { PlacementID int `json:"placement_id,omitempty"` Keywords string `json:"keywords,omitempty"` @@ -115,181 +85,7 @@ type appnexusReqExt struct { var maxImpsPerReq = 10 -func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - anReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), supportedMediaTypes) - - if err != nil { - return nil, err - } - uri := a.URI - for i, unit := range bidder.AdUnits { - var params appnexusParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, err - } - // Accept legacy Appnexus parameters if we don't have modern ones - // Don't worry if both is set as validation rules should prevent, and this is temporary anyway. - if params.PlacementId == 0 && params.LegacyPlacementId != 0 { - params.PlacementId = params.LegacyPlacementId - } - if params.InvCode == "" && params.LegacyInvCode != "" { - params.InvCode = params.LegacyInvCode - } - if params.TrafficSourceCode == "" && params.LegacyTrafficSourceCode != "" { - params.TrafficSourceCode = params.LegacyTrafficSourceCode - } - - if params.PlacementId == 0 && (params.InvCode == "" || params.Member == "") { - return nil, &errortypes.BadInput{ - Message: "No placement or member+invcode provided", - } - } - - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(anReq.Imp) <= i { - break - } - if params.InvCode != "" { - anReq.Imp[i].TagID = params.InvCode - if params.Member != "" { - // this assumes that the same member ID is used across all tags, which should be the case - uri = appendMemberId(a.URI, params.Member) - } - - } - if params.Reserve > 0 { - anReq.Imp[i].BidFloor = params.Reserve // TODO: we need to factor in currency here if non-USD - } - if anReq.Imp[i].Banner != nil && params.Position != "" { - if params.Position == "above" { - anReq.Imp[i].Banner.Pos = openrtb2.AdPositionAboveTheFold.Ptr() - } else if params.Position == "below" { - anReq.Imp[i].Banner.Pos = openrtb2.AdPositionBelowTheFold.Ptr() - } - } - - kvs := make([]string, 0, len(params.Keywords)*2) - for _, kv := range params.Keywords { - if len(kv.Values) == 0 { - kvs = append(kvs, kv.Key) - } else { - for _, val := range kv.Values { - kvs = append(kvs, fmt.Sprintf("%s=%s", kv.Key, val)) - } - - } - } - - keywordStr := strings.Join(kvs, ",") - - impExt := appnexusImpExt{Appnexus: appnexusImpExtAppnexus{ - PlacementID: params.PlacementId, - TrafficSourceCode: params.TrafficSourceCode, - Keywords: keywordStr, - UsePmtRule: params.UsePmtRule, - PrivateSizes: params.PrivateSizes, - }} - anReq.Imp[i].Ext, err = json.Marshal(&impExt) - } - - reqJSON, err := json.Marshal(anReq) - if err != nil { - return nil, err - } - - debug := &pbs.BidderDebug{ - RequestURI: uri, - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", uri, bytes.NewBuffer(reqJSON)) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - anResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - debug.StatusCode = anResp.StatusCode - - if anResp.StatusCode == 204 { - return nil, nil - } - - defer anResp.Body.Close() - body, err := ioutil.ReadAll(anResp.Body) - if err != nil { - return nil, err - } - responseBody := string(body) - - if anResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, responseBody), - } - } - - if anResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, responseBody), - } - } - - if req.IsDebug { - debug.ResponseBody = responseBody - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, err - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - NURL: bid.NURL, - } - - var impExt appnexusBidExt - if err := json.Unmarshal(bid.Ext, &impExt); err == nil { - if mediaType, err := getMediaTypeForBid(&impExt); err == nil { - pbid.CreativeMediaType = string(mediaType) - bids = append(bids, &pbid) - } - } - } - } - - return bids, nil -} - -func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { memberIds := make(map[string]bool) errs := make([]error, 0, len(request.Imp)) @@ -383,7 +179,7 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad requests := make([]*adapters.RequestData, 0, len(podImps)) for _, podImps := range podImps { - reqExt.Appnexus.AdPodId = generatePodId() + reqExt.Appnexus.AdPodId = generatePodID() reqs, errors := splitRequests(podImps, request, reqExt, thisURI, errs) requests = append(requests, reqs...) @@ -395,7 +191,7 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad return splitRequests(imps, request, reqExt, thisURI, errs) } -func generatePodId() string { +func generatePodID() string { val := rand.Int63() return fmt.Sprint(val) } @@ -561,7 +357,7 @@ func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string { return strings.Join(kvs, ",") } -func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []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 } @@ -596,7 +392,7 @@ func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa bid.Cat = []string{iabCategory} } else if len(bid.Cat) > 1 { //create empty categories array to force bid to be rejected - bid.Cat = make([]string, 0, 0) + bid.Cat = make([]string, 0) } impVideo := &openrtb_ext.ExtBidPrebidVideo{ @@ -638,7 +434,7 @@ func getMediaTypeForBid(bid *appnexusBidExt) (openrtb_ext.BidType, error) { } // getIabCategoryForBid maps an appnexus brand id to an IAB category. -func (a *AppNexusAdapter) getIabCategoryForBid(bid *appnexusBidExt) (string, error) { +func (a *adapter) getIabCategoryForBid(bid *appnexusBidExt) (string, error) { brandIDString := strconv.Itoa(bid.Appnexus.BrandCategory) if iabCategory, ok := a.iabCategoryMap[brandIDString]; ok { return iabCategory, nil @@ -657,7 +453,7 @@ func appendMemberId(uri string, memberId string) string { // Builder builds a new instance of the AppNexus adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - bidder := &AppNexusAdapter{ + bidder := &adapter{ URI: config.Endpoint, iabCategoryMap: loadCategoryMapFromFileSystem(), hbSource: resolvePlatformID(config.PlatformID), @@ -665,16 +461,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -// NewAppNexusLegacyAdapter builds a legacy version of the AppNexus adapter. -func NewAppNexusLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpoint, platformID string) *AppNexusAdapter { - return &AppNexusAdapter{ - http: adapters.NewHTTPAdapter(httpConfig), - URI: endpoint, - iabCategoryMap: loadCategoryMapFromFileSystem(), - hbSource: resolvePlatformID(platformID), - } -} - func resolvePlatformID(platformID string) int { if len(platformID) > 0 { if val, err := strconv.Atoi(platformID); err == nil { diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index cad52348134..6399be1a5bb 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -1,29 +1,17 @@ package appnexus import ( - "bytes" - "context" "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" "regexp" "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/stretchr/testify/assert" - - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "fmt" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { @@ -58,7 +46,7 @@ func TestMemberQueryParam(t *testing.T) { } func TestVideoSinglePod(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -96,7 +84,7 @@ func TestVideoSinglePod(t *testing.T) { } func TestVideoSinglePodManyImps(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -154,7 +142,7 @@ func TestVideoSinglePodManyImps(t *testing.T) { } func TestVideoTwoPods(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -206,7 +194,7 @@ func TestVideoTwoPods(t *testing.T) { } func TestVideoTwoPodsManyImps(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -281,396 +269,3 @@ func TestVideoTwoPodsManyImps(t *testing.T) { assert.Len(t, podIds, 2, "Incorrect number of unique pod ids") } - -// ---------------------------------------------------------------------------- -// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb2. - -type anTagInfo struct { - code string - invCode string - placementID int - trafficSourceCode string - in_keywords string - out_keywords string - reserve float64 - position string - bid float64 - content string - mediaType string -} - -type anBidInfo struct { - memberID string - domain string - page string - accountID int - siteID int - tags []anTagInfo - deviceIP string - deviceUA string - buyerUID string - delay time.Duration -} - -var andata anBidInfo - -func DummyAppNexusServer(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 - } - - memberID := r.FormValue("member_id") - if memberID != andata.memberID { - http.Error(w, fmt.Sprintf("Member ID '%s' doesn't match '%s", memberID, andata.memberID), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - ID: breq.ID, - BidID: "a-random-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "Buyer Member ID", - Bid: make([]openrtb2.Bid, 0, 2), - }, - }, - } - - for i, imp := range breq.Imp { - var aix appnexusImpExt - err = json.Unmarshal(imp.Ext, &aix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Either placementID or member+invCode must be specified - has_placement := false - if aix.Appnexus.PlacementID != 0 { - if aix.Appnexus.PlacementID != andata.tags[i].placementID { - http.Error(w, fmt.Sprintf("Placement ID '%d' doesn't match '%d", aix.Appnexus.PlacementID, - andata.tags[i].placementID), http.StatusInternalServerError) - return - } - has_placement = true - } - if memberID != "" && imp.TagID != "" { - if imp.TagID != andata.tags[i].invCode { - http.Error(w, fmt.Sprintf("Inv Code '%s' doesn't match '%s", imp.TagID, - andata.tags[i].invCode), http.StatusInternalServerError) - return - } - has_placement = true - } - if !has_placement { - http.Error(w, fmt.Sprintf("Either placement or member+inv not present"), http.StatusInternalServerError) - return - } - - if aix.Appnexus.Keywords != andata.tags[i].out_keywords { - http.Error(w, fmt.Sprintf("Keywords '%s' doesn't match '%s", aix.Appnexus.Keywords, - andata.tags[i].out_keywords), http.StatusInternalServerError) - return - } - - if aix.Appnexus.TrafficSourceCode != andata.tags[i].trafficSourceCode { - http.Error(w, fmt.Sprintf("Traffic source code '%s' doesn't match '%s", aix.Appnexus.TrafficSourceCode, - andata.tags[i].trafficSourceCode), http.StatusInternalServerError) - return - } - if imp.BidFloor != andata.tags[i].reserve { - http.Error(w, fmt.Sprintf("Bid floor '%.2f' doesn't match '%.2f", imp.BidFloor, - andata.tags[i].reserve), http.StatusInternalServerError) - return - } - if imp.Banner == nil && imp.Video == nil { - http.Error(w, fmt.Sprintf("No banner or app object sent"), http.StatusInternalServerError) - return - } - if (imp.Banner == nil && andata.tags[i].mediaType == "banner") || (imp.Banner != nil && andata.tags[i].mediaType != "banner") { - http.Error(w, fmt.Sprintf("Invalid impression type - banner"), http.StatusInternalServerError) - return - } - if (imp.Video == nil && andata.tags[i].mediaType == "video") || (imp.Video != nil && andata.tags[i].mediaType != "video") { - http.Error(w, fmt.Sprintf("Invalid impression type - video"), http.StatusInternalServerError) - return - } - - if imp.Banner != nil { - if len(imp.Banner.Format) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.banner.format array"), http.StatusInternalServerError) - return - } - if andata.tags[i].position == "above" && *imp.Banner.Pos != openrtb2.AdPosition(1) { - http.Error(w, fmt.Sprintf("Mismatch in position - expected 1 for atf"), http.StatusInternalServerError) - return - } - if andata.tags[i].position == "below" && *imp.Banner.Pos != openrtb2.AdPosition(3) { - http.Error(w, fmt.Sprintf("Mismatch in position - expected 3 for btf"), http.StatusInternalServerError) - return - } - } - if imp.Video != nil { - // TODO: add more validations - if len(imp.Video.MIMEs) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.mimes array"), http.StatusInternalServerError) - return - } - if len(imp.Video.Protocols) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.protocols array"), http.StatusInternalServerError) - return - } - for _, protocol := range imp.Video.Protocols { - if protocol < 1 || protocol > 8 { - http.Error(w, fmt.Sprintf("Invalid video protocol %d", protocol), http.StatusInternalServerError) - return - } - } - } - - resBid := openrtb2.Bid{ - ID: "random-id", - ImpID: imp.ID, - Price: andata.tags[i].bid, - AdM: andata.tags[i].content, - Ext: json.RawMessage(fmt.Sprintf(`{"appnexus":{"bid_ad_type":%d}}`, bidTypeToInt(andata.tags[i].mediaType))), - } - - if imp.Video != nil { - resBid.Attr = []openrtb2.CreativeAttribute{openrtb2.CreativeAttribute(6)} - } - resp.SeatBid[0].Bid = append(resp.SeatBid[0].Bid, resBid) - } - - // TODO: are all of these valid for app? - if breq.Site == nil { - http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) - return - } - if breq.Site.Domain != andata.domain { - http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, andata.domain), http.StatusInternalServerError) - return - } - if breq.Site.Page != andata.page { - http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, andata.page), http.StatusInternalServerError) - return - } - if breq.Device.UA != andata.deviceUA { - http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s", breq.Device.UA, andata.deviceUA), http.StatusInternalServerError) - return - } - if breq.Device.IP != andata.deviceIP { - http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s", breq.Device.IP, andata.deviceIP), http.StatusInternalServerError) - return - } - if breq.User.BuyerUID != andata.buyerUID { - http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s", breq.User.BuyerUID, andata.buyerUID), http.StatusInternalServerError) - return - } - - if andata.delay > 0 { - <-time.After(andata.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 bidTypeToInt(bidType string) int { - switch bidType { - case "banner": - return 0 - case "video": - return 1 - case "audio": - return 2 - case "native": - return 3 - default: - return -1 - } -} -func TestAppNexusLegacyBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyAppNexusServer)) - defer server.Close() - - andata = anBidInfo{ - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - tags: make([]anTagInfo, 2), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "23482348223", - memberID: "958", - } - andata.tags[0] = anTagInfo{ - code: "first-tag", - placementID: 8394, - bid: 1.67, - trafficSourceCode: "ppc-exchange", - content: "huh", - in_keywords: "[{ \"key\": \"genre\", \"value\": [\"jazz\", \"pop\"] }, {\"key\": \"myEmptyKey\", \"value\": []}]", - out_keywords: "genre=jazz,genre=pop,myEmptyKey", - reserve: 1.50, - position: "below", - mediaType: "banner", - } - andata.tags[1] = anTagInfo{ - code: "second-tag", - invCode: "leftbottom", - bid: 3.22, - trafficSourceCode: "taboola", - content: "yow!", - in_keywords: "[{ \"key\": \"genre\", \"value\": [\"rock\", \"pop\"] }, {\"key\": \"myKey\", \"value\": [\"myVal\"]}]", - out_keywords: "genre=rock,genre=pop,myKey=myVal", - reserve: 0.75, - position: "above", - mediaType: "video", - } - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewAppNexusLegacyAdapter(&conf, server.URL, "") - - pbin := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 2), - } - for i, tag := range andata.tags { - var params json.RawMessage - if tag.placementID > 0 { - params = json.RawMessage(fmt.Sprintf("{\"placementId\": %d, \"member\": \"%s\", \"keywords\": %s, "+ - "\"trafficSourceCode\": \"%s\", \"reserve\": %.3f, \"position\": \"%s\"}", - tag.placementID, andata.memberID, tag.in_keywords, tag.trafficSourceCode, tag.reserve, tag.position)) - } else { - params = json.RawMessage(fmt.Sprintf("{\"invCode\": \"%s\", \"member\": \"%s\", \"keywords\": %s, "+ - "\"trafficSourceCode\": \"%s\", \"reserve\": %.3f, \"position\": \"%s\"}", - tag.invCode, andata.memberID, tag.in_keywords, tag.trafficSourceCode, tag.reserve, tag.position)) - } - - pbin.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 600, - }, - { - W: 300, - H: 250, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "appnexus", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: params, - }, - }, - } - if tag.mediaType == "video" { - pbin.AdUnits[i].Video = pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{2, 3}, - } - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbin) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - - req := httptest.NewRequest("POST", server.URL, body) - req.Header.Add("Referer", andata.page) - req.Header.Add("User-Agent", andata.deviceUA) - req.Header.Add("X-Real-IP", andata.deviceIP) - - pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) - pc.TrySync("adnxs", andata.buyerUID) - 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 != "appnexus" { - 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 andata.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.CreativeMediaType != tag.mediaType { - t.Errorf("Incorrect Creative MediaType '%s'. Expected '%s'", bid.CreativeMediaType, tag.mediaType) - } - if bid.BidderCode != "appnexus" { - 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.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 - andata.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/connectad/connectad.go b/adapters/connectad/connectad.go index 9827ebcea7b..5c30e3a6adc 100644 --- a/adapters/connectad/connectad.go +++ b/adapters/connectad/connectad.go @@ -18,10 +18,6 @@ type ConnectAdAdapter struct { endpoint string } -type connectadImpExt struct { - ConnectAd openrtb_ext.ExtImpConnectAd `json:"connectad"` -} - // Builder builds a new instance of the ConnectAd adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &ConnectAdAdapter{ diff --git a/adapters/consumable/instant.go b/adapters/consumable/instant.go index 5a32fef8837..a6162d44e22 100644 --- a/adapters/consumable/instant.go +++ b/adapters/consumable/instant.go @@ -9,7 +9,7 @@ type instant interface { // Send a real instance when you construct it in adapter_map.go type realInstant struct{} -func (_ realInstant) Now() time.Time { +func (realInstant) Now() time.Time { return time.Now() } diff --git a/adapters/consumable/utils.go b/adapters/consumable/utils.go deleted file mode 100644 index 64e4872c619..00000000000 --- a/adapters/consumable/utils.go +++ /dev/null @@ -1,20 +0,0 @@ -package consumable - -import ( - netUrl "net/url" -) - -/** - * Creates a snippet of HTML that retrieves the specified `url` - * Returns HTML snippet that contains the img src = set to `url` - */ -func createTrackPixelHtml(url *string) string { - if url == nil { - return "" - } - - escapedUrl := netUrl.QueryEscape(*url) - img := "
" + - "
" - return img -} diff --git a/adapters/conversant/cnvr_legacy.go b/adapters/conversant/cnvr_legacy.go deleted file mode 100644 index eff1afc5d32..00000000000 --- a/adapters/conversant/cnvr_legacy.go +++ /dev/null @@ -1,291 +0,0 @@ -package conversant - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "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 ConversantLegacyAdapter struct { - http *adapters.HTTPAdapter - URI string -} - -// Corresponds to the bidder name in cookies and requests -func (a *ConversantLegacyAdapter) Name() string { - return "conversant" -} - -// Return true so no request will be sent unless user has been sync'ed. -func (a *ConversantLegacyAdapter) SkipNoCookies() bool { - return true -} - -type conversantParams struct { - SiteID string `json:"site_id"` - Secure *int8 `json:"secure"` - TagID string `json:"tag_id"` - Position *int8 `json:"position"` - BidFloor float64 `json:"bidfloor"` - Mobile *int8 `json:"mobile"` - MIMEs []string `json:"mimes"` - API []int8 `json:"api"` - Protocols []int8 `json:"protocols"` - MaxDuration *int64 `json:"maxduration"` -} - -func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - cnvrReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - return nil, err - } - - // Create a map of impression objects for both request creation - // and response parsing. - - impMap := make(map[string]*openrtb2.Imp, len(cnvrReq.Imp)) - for idx := range cnvrReq.Imp { - impMap[cnvrReq.Imp[idx].ID] = &cnvrReq.Imp[idx] - } - - // Fill in additional info from custom params - - for _, unit := range bidder.AdUnits { - var params conversantParams - - imp := impMap[unit.Code] - if imp == nil { - // Skip ad units that do not have corresponding impressions. - continue - } - - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - - // Fill in additional Site info - if params.SiteID != "" { - if cnvrReq.Site != nil { - cnvrReq.Site.ID = params.SiteID - } - if cnvrReq.App != nil { - cnvrReq.App.ID = params.SiteID - } - } - - if params.Mobile != nil && !(cnvrReq.Site == nil) { - cnvrReq.Site.Mobile = *params.Mobile - } - - // Fill in additional impression info - - imp.DisplayManager = "prebid-s2s" - imp.DisplayManagerVer = "1.0.1" - imp.BidFloor = params.BidFloor - imp.TagID = params.TagID - - var position *openrtb2.AdPosition - if params.Position != nil { - position = openrtb2.AdPosition(*params.Position).Ptr() - } - - if imp.Banner != nil { - imp.Banner.Pos = position - } else if imp.Video != nil { - imp.Video.Pos = position - - if len(params.API) > 0 { - imp.Video.API = make([]openrtb2.APIFramework, 0, len(params.API)) - for _, api := range params.API { - imp.Video.API = append(imp.Video.API, openrtb2.APIFramework(api)) - } - } - - // Include protocols, mimes, and max duration if specified - // These properties can also be specified in ad unit's video object, - // but are overridden if the custom params object also contains them. - - if len(params.Protocols) > 0 { - imp.Video.Protocols = make([]openrtb2.Protocol, 0, len(params.Protocols)) - for _, protocol := range params.Protocols { - imp.Video.Protocols = append(imp.Video.Protocols, openrtb2.Protocol(protocol)) - } - } - - if len(params.MIMEs) > 0 { - imp.Video.MIMEs = make([]string, len(params.MIMEs)) - copy(imp.Video.MIMEs, params.MIMEs) - } - - if params.MaxDuration != nil { - imp.Video.MaxDuration = *params.MaxDuration - } - } - - // Take care not to override the global secure flag - - if (imp.Secure == nil || *imp.Secure == 0) && params.Secure != nil { - imp.Secure = params.Secure - } - } - - // Do a quick check on required parameters - - if cnvrReq.Site != nil && cnvrReq.Site.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing site id", - } - } - - if cnvrReq.App != nil && cnvrReq.App.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing app id", - } - } - - // Start capturing debug info - - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if cnvrReq.Device == nil { - cnvrReq.Device = &openrtb2.Device{} - } - - // Convert request to json to be sent over http - - j, _ := json.Marshal(cnvrReq) - - if req.IsDebug { - debug.RequestBody = string(j) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(j)) - httpReq.Header.Add("Content-Type", "application/json") - httpReq.Header.Add("Accept", "application/json") - - resp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if req.IsDebug { - debug.StatusCode = resp.StatusCode - } - - if resp.StatusCode == 204 { - return nil, nil - } - - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } - } - - if resp.StatusCode != 200 { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } - } - - if req.IsDebug { - debug.ResponseBody = string(body) - } - - var bidResp openrtb2.BidResponse - - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, seatbid := range bidResp.SeatBid { - for _, bid := range seatbid.Bid { - if bid.Price <= 0 { - continue - } - - imp := impMap[bid.ImpID] - if imp == nil { - // All returned bids should have a matching impression - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression id '%s'", bid.ImpID), - } - } - - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbsBid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - Price: bid.Price, - Creative_id: bid.CrID, - BidderCode: bidder.BidderCode, - } - - if imp.Video != nil { - pbsBid.CreativeMediaType = "video" - pbsBid.NURL = bid.AdM // Assign to NURL so it'll be interpreted as a vastUrl - pbsBid.Width = imp.Video.W - pbsBid.Height = imp.Video.H - } else { - pbsBid.CreativeMediaType = "banner" - pbsBid.NURL = bid.NURL - pbsBid.Adm = bid.AdM - pbsBid.Width = bid.W - pbsBid.Height = bid.H - } - - bids = append(bids, &pbsBid) - } - } - - if len(bids) == 0 { - return nil, nil - } - - return bids, nil -} - -func NewConversantLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *ConversantLegacyAdapter { - a := adapters.NewHTTPAdapter(config) - - return &ConversantLegacyAdapter{ - http: a, - URI: uri, - } -} diff --git a/adapters/conversant/cnvr_legacy_test.go b/adapters/conversant/cnvr_legacy_test.go deleted file mode 100644 index fc34a93fae2..00000000000 --- a/adapters/conversant/cnvr_legacy_test.go +++ /dev/null @@ -1,853 +0,0 @@ -package conversant - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" -) - -// Constants - -const ExpectedSiteID string = "12345" -const ExpectedDisplayManager string = "prebid-s2s" -const ExpectedBuyerUID string = "AQECT_o7M1FLbQJK8QFmAQEBAQE" -const ExpectedNURL string = "http://test.dotomi.com" -const ExpectedAdM string = "" -const ExpectedCrID string = "98765" - -const DefaultParam = `{"site_id": "12345"}` - -// Test properties of Adapter interface - -func TestConversantProperties(t *testing.T) { - an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - assertNotEqual(t, an.Name(), "", "Missing family name") - assertTrue(t, an.SkipNoCookies(), "SkipNoCookies should be true") -} - -// Test empty bid requests - -func TestConversantEmptyBid(t *testing.T) { - an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := an.Call(ctx, &pbReq, &pbBidder) - assertTrue(t, err != nil, "No error received for an invalid request") -} - -// Test required parameters, which is just the site id for now - -func TestConversantRequiredParameters(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - ctx := context.TODO() - - testParams := func(params ...string) (pbs.PBSBidSlice, error) { - req, err := CreateBannerRequest(params...) - if err != nil { - return nil, err - } - return an.Call(ctx, req, req.Bidders[0]) - } - - var err error - - if _, err = testParams(`{}`); err == nil { - t.Fatal("Failed to catch missing site id") - } -} - -// Test handling of 404 - -func TestConversantBadStatus(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assertTrue(t, err != nil, "Failed to catch 404 error") -} - -// Test handling of HTTP timeout - -func TestConversantTimeout(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - // Create a context that expires before http returns - - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - // Create a basic request - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - // Attempt to process the request, which should hit a timeout - // immediately - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err == nil || err != context.DeadlineExceeded { - t.Fatal("No timeout recevied for timed out request", err) - } -} - -// Test handling of 204 - -func TestConversantNoBid(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if resp != nil || err != nil { - t.Fatal("Failed to handle empty bid", err) - } -} - -// Verify an outgoing openrtp request is created correctly - -func TestConversantRequestDefault(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, imp.TagID, "", "Request tag id") - assertTrue(t, imp.Banner.Pos == nil, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify inapp video request -func TestConversantInappVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - requestParam := `{"secure": 1, "site_id": "12345"}` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(requestParam) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - assertEqual(t, lastReq.App.ID, "12345", "App Id") -} - -// Verify inapp video request -func TestConversantInappBannerRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "secure": 1, - "site_id": "12345", - "tag_id": "top", - "position": 2, - "bidfloor": 1.01 }` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - - ctx := context.TODO() - pbReq, _ := CreateBannerRequest(param) - pbReq, err := ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, lastReq.App.ID, "12345", "App Id") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify an outgoing openrtp request with additional conversant parameters is -// processed correctly - -func TestConversantRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile": 1 }` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 1, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 1, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "top", "Request tag id") - assertEqual(t, int(*imp.Banner.Pos), 2, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify openrtp responses are converted correctly - -func TestConversantResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, ExpectedAdM, "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedNURL, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, *imps[i].Banner.W, "Bad width in response") - assertEqual(t, bid.Height, *imps[i].Banner.H, "Bad height in response") - } - } -} - -// Test video request - -func TestConversantBasicVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/mp4", "Requst video MIMEs type") - assertTrue(t, imp.Video.Protocols == nil, "Request video protocols") - assertEqual(t, imp.Video.MaxDuration, int64(0), "Request video 0 max duration") - assertTrue(t, imp.Video.API == nil, "Request video api should be nil") -} - -// Test video request with parameters in custom params object - -func TestConversantVideoRequestWithParams(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01, - "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "api": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") - assertEqual(t, len(imp.Video.API), 2, "Request video api should be nil") - assertEqual(t, imp.Video.API[0], openrtb2.APIFramework(1), "Request video api 1") - assertEqual(t, imp.Video.API[1], openrtb2.APIFramework(2), "Request video api 2") -} - -// Test video request with parameters in the video object - -func TestConversantVideoRequestWithParams2(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(param) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("Failed to convert to a video request", err) - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("Failed to parse video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") -} - -// Test video responses - -func TestConversantVideoResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, "", "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedAdM, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, imps[i].Video.W, "Bad width in response") - assertEqual(t, bid.Height, imps[i].Video.H, "Bad height in response") - } - } -} - -// Helpers to create a banner and video requests - -func CreateRequest(params ...string) *pbs.PBSRequest { - num := len(params) - - req := pbs.PBSRequest{ - Tid: "t-000", - AccountID: "1", - AdUnits: make([]pbs.AdUnit, num), - } - - for i := 0; i < num; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("au-%03d", i), - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "conversant", - BidID: fmt.Sprintf("b-%03d", i), - Params: json.RawMessage(params[i]), - }, - }, - } - } - - return &req -} - -// Convert a request to a video request by adding required properties - -func ConvertToVideoRequest(req *pbs.PBSRequest, videoParams ...string) (*pbs.PBSRequest, error) { - for i := 0; i < len(req.AdUnits); i++ { - video := pbs.PBSVideo{} - if i < len(videoParams) { - err := json.Unmarshal([]byte(videoParams[i]), &video) - if err != nil { - return nil, err - } - } - - if video.Mimes == nil { - video.Mimes = []string{"video/mp4"} - } - - req.AdUnits[i].Video = video - req.AdUnits[i].MediaTypes = []string{"video"} - } - - return req, nil -} - -// Convert a request to an app request by adding required properties -func ConvertToAppRequest(req *pbs.PBSRequest, appParams string) (*pbs.PBSRequest, error) { - app := new(openrtb2.App) - err := json.Unmarshal([]byte(appParams), &app) - if err == nil { - req.App = app - } - - return req, nil -} - -// Feed the request thru the prebid parser so user id and -// other private properties are defined - -func ParseRequest(req *pbs.PBSRequest) (*pbs.PBSRequest, error) { - body := new(bytes.Buffer) - _ = json.NewEncoder(body).Encode(req) - - // Need to pass the conversant user id thru uid cookie - - httpReq := httptest.NewRequest("POST", "/foo", body) - cookie := usersync.NewCookie() - _ = cookie.TrySync("conversant", ExpectedBuyerUID) - httpReq.Header.Set("Cookie", cookie.ToHTTPCookie(90*24*time.Hour).String()) - httpReq.Header.Add("Referer", "http://example.com") - cache, _ := dummycache.New() - hcc := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cache, &hcc) - - return parsedReq, err -} - -// A helper to create a banner request - -func CreateBannerRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ParseRequest(req) - return req, err -} - -// A helper to create a video request - -func CreateVideoRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ConvertToVideoRequest(req) - if err != nil { - return nil, err - } - req, err = ParseRequest(req) - return req, err -} - -// Helper to create a test http server that receives and generate openrtb requests and responses - -func CreateServer(prices ...float64) (*httptest.Server, *openrtb2.BidRequest) { - var lastBidRequest openrtb2.BidRequest - server := httptest.NewServer(http.HandlerFunc(func(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 bidReq openrtb2.BidRequest - var price float64 - var bids []openrtb2.Bid - var bid openrtb2.Bid - - err = json.Unmarshal(body, &bidReq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - lastBidRequest = bidReq - - for i, imp := range bidReq.Imp { - if i < len(prices) { - price = prices[i] - } else { - price = 0 - } - - if price > 0 { - bid = openrtb2.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: price, - NURL: ExpectedNURL, - AdM: ExpectedAdM, - CrID: ExpectedCrID, - } - - if imp.Banner != nil { - bid.W = *imp.Banner.W - bid.H = *imp.Banner.H - } else if imp.Video != nil { - bid.W = imp.Video.W - bid.H = imp.Video.H - } - } else { - bid = openrtb2.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: 0, - } - } - - bids = append(bids, bid) - } - - if len(bids) == 0 { - w.WriteHeader(http.StatusNoContent) - } else { - js, _ := json.Marshal(openrtb2.BidResponse{ - ID: bidReq.ID, - SeatBid: []openrtb2.SeatBid{ - { - Bid: bids, - }, - }, - }) - - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write(js) - } - }), - ) - - return server, &lastBidRequest -} - -// Helper to remove impressions with $0 bids - -func FilterZeroPrices(prices []float64, imps []openrtb2.Imp) ([]float64, []openrtb2.Imp) { - prices2 := make([]float64, 0) - imps2 := make([]openrtb2.Imp, 0) - - for i := range prices { - if prices[i] > 0 { - prices2 = append(prices2, prices[i]) - imps2 = append(imps2, imps[i]) - } - } - - return prices2, imps2 -} - -// Helpers to test equality - -func assertEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected != actual { - msg = fmt.Sprintf("%s: act(%v) != exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertNotEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected == actual { - msg = fmt.Sprintf("%s: act(%v) == exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertTrue(t *testing.T, val bool, msg string) { - if val == false { - msg = fmt.Sprintf("%s: is false but should be true", msg) - t.Fatal(msg) - } -} - -func assertFalse(t *testing.T, val bool, msg string) { - if val == true { - msg = fmt.Sprintf("%s: is true but should be false", msg) - t.Fatal(msg) - } -} diff --git a/adapters/deepintent/deepintent.go b/adapters/deepintent/deepintent.go index b5b0fd54c5d..0853bb8b405 100644 --- a/adapters/deepintent/deepintent.go +++ b/adapters/deepintent/deepintent.go @@ -20,10 +20,6 @@ type DeepintentAdapter struct { URI string } -type deepintentParams struct { - tagId string `json:"tagId"` -} - // Builder builds a new instance of the Deepintent adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &DeepintentAdapter{ diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index 409290c110d..aa4a6f79053 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -13,10 +13,6 @@ import ( "github.com/prebid/prebid-server/adapters/adapterstest" ) -var ( - bidRequest string -) - func TestFetchParams(t *testing.T) { var w, h int = 300, 250 diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index 375248137ad..62bcc08d7cb 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -178,7 +178,6 @@ func TestImpFiltering(t *testing.T) { } type mockBidder struct { - gotRequest *openrtb2.BidRequest } func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index c79eda31040..1cfec69322d 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -1,259 +1,27 @@ package ix import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "sort" "strings" - "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" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/mxmCherry/openrtb/v15/native1" + native1response "github.com/mxmCherry/openrtb/v15/native1/response" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type IxAdapter struct { - http *adapters.HTTPAdapter URI string maxRequests int } -func (a *IxAdapter) Name() string { - return string(openrtb_ext.BidderIx) -} - -func (a *IxAdapter) SkipNoCookies() bool { - return false -} - -type indexParams struct { - SiteID string `json:"siteId"` -} - -type ixBidResult struct { - Request *callOneObject - StatusCode int - ResponseBody string - Bid *pbs.PBSBid - Error error -} - -type callOneObject struct { - requestJSON bytes.Buffer - width int64 - height int64 - bidType string -} - -func (a *IxAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - var prioritizedRequests, requests []callOneObject - - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - indexReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - if err != nil { - return nil, err - } - - indexReqImp := indexReq.Imp - for i, unit := range bidder.AdUnits { - // Supposedly fixes some segfaults - if len(indexReqImp) <= i { - break - } - - var params indexParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("unmarshal params '%s' failed: %v", unit.Params, err), - } - } - - if params.SiteID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing siteId param", - } - } - - for sizeIndex, format := range unit.Sizes { - // Only grab this ad unit. Not supporting multi-media-type adunit yet. - thisImp := indexReqImp[i] - - thisImp.TagID = unit.Code - if thisImp.Banner != nil { - thisImp.Banner.Format = []openrtb2.Format{format} - thisImp.Banner.W = &format.W - thisImp.Banner.H = &format.H - } - indexReq.Imp = []openrtb2.Imp{thisImp} - // Index spec says "adunit path representing ad server inventory" but we don't have this - // ext is DFP div ID and KV pairs if avail - //indexReq.Imp[i].Ext = json.RawMessage("{}") - - if indexReq.Site != nil { - // Any objects pointed to by indexReq *must not be mutated*, or we will get race conditions. - siteCopy := *indexReq.Site - siteCopy.Publisher = &openrtb2.Publisher{ID: params.SiteID} - indexReq.Site = &siteCopy - } - - bidType := "" - if thisImp.Banner != nil { - bidType = string(openrtb_ext.BidTypeBanner) - } else if thisImp.Video != nil { - bidType = string(openrtb_ext.BidTypeVideo) - } - j, _ := json.Marshal(indexReq) - request := callOneObject{requestJSON: *bytes.NewBuffer(j), width: format.W, height: format.H, bidType: bidType} - - // prioritize slots over sizes - if sizeIndex == 0 { - prioritizedRequests = append(prioritizedRequests, request) - } else { - requests = append(requests, request) - } - } - } - - // cap the number of requests to maxRequests - requests = append(prioritizedRequests, requests...) - if len(requests) > a.maxRequests { - requests = requests[:a.maxRequests] - } - - if len(requests) == 0 { - return nil, &errortypes.BadInput{ - Message: "Invalid ad unit/imp/size", - } - } - - ch := make(chan ixBidResult) - for _, request := range requests { - go func(bidder *pbs.PBSBidder, request callOneObject) { - result, err := a.callOne(ctx, request.requestJSON) - result.Request = &request - result.Error = err - if result.Bid != nil { - result.Bid.BidderCode = bidder.BidderCode - result.Bid.BidID = bidder.LookupBidID(result.Bid.AdUnitCode) - result.Bid.Width = request.width - result.Bid.Height = request.height - result.Bid.CreativeMediaType = request.bidType - - 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, request) - } - - bids := make(pbs.PBSBidSlice, 0) - for i := 0; i < len(requests); i++ { - result := <-ch - if result.Bid != nil && result.Bid.Price != 0 { - bids = append(bids, result.Bid) - } - - if req.IsDebug { - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - RequestBody: result.Request.requestJSON.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 (a *IxAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (ixBidResult, error) { - var result ixBidResult - - httpReq, _ := http.NewRequest("POST", a.URI, &reqJSON) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - ixResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return result, err - } - - result.StatusCode = ixResp.StatusCode - - if ixResp.StatusCode == http.StatusNoContent { - return result, nil - } - - if ixResp.StatusCode == http.StatusBadRequest { - return result, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), - } - } - - if ixResp.StatusCode != http.StatusOK { - return result, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), - } - } - - defer ixResp.Body.Close() - body, err := ioutil.ReadAll(ixResp.Body) - if err != nil { - return result, err - } - result.ResponseBody = string(body) - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return result, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Error parsing response: %v", err), - } - } - - if len(bidResp.SeatBid) == 0 { - return result, nil - } - if len(bidResp.SeatBid[0].Bid) == 0 { - return result, nil - } - bid := bidResp.SeatBid[0].Bid[0] - - pbid := pbs.PBSBid{ - AdUnitCode: bid.ImpID, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - - result.Bid = &pbid - return result, nil -} - func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { nImp := len(request.Imp) if nImp > a.maxRequests { @@ -454,14 +222,6 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque return bidderResponse, errs } -func NewIxLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *IxAdapter { - return &IxAdapter{ - http: adapters.NewHTTPAdapter(config), - URI: endpoint, - maxRequests: 20, - } -} - // Builder builds a new instance of the Ix adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &IxAdapter{ diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index d292273a92c..fc1d0f9a0a2 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -1,22 +1,15 @@ package ix import ( - "context" "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/http/httptest" "testing" - "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const endpoint string = "http://host/endpoint" @@ -31,698 +24,6 @@ func TestJsonSamples(t *testing.T) { } } -// Tests for the legacy, non-openrtb code. -// They can be removed after the legacy interface is deprecated. - -func getAdUnit() pbs.PBSAdUnit { - return pbs.PBSAdUnit{ - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{\"siteId\":\"12\"}"), - } -} - -func getVideoAdUnit() pbs.PBSAdUnit { - return pbs.PBSAdUnit{ - Code: "unitCodeVideo", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - BidID: "bididvideo", - Sizes: []openrtb2.Format{ - { - W: 100, - H: 75, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{2, 3}, - }, - Params: json.RawMessage("{\"siteId\":\"12\"}"), - } -} - -func getOpenRTBBid(i openrtb2.Imp) openrtb2.Bid { - return openrtb2.Bid{ - ID: fmt.Sprintf("%d", rand.Intn(1000)), - ImpID: i.ID, - Price: 1.0, - AdM: "Content", - } -} - -func newAdapter(endpoint string) *IxAdapter { - return NewIxLegacyAdapter(adapters.DefaultHTTPAdapterConfig, endpoint) -} - -func dummyIXServer(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 - } - - impression := breq.Imp[0] - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - getOpenRTBBid(impression), - }, - }, - }, - } - - 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 TestIxSkipNoCookies(t *testing.T) { - if newAdapter(endpoint).SkipNoCookies() { - t.Fatalf("SkipNoCookies must return false") - } -} - -func TestIxInvalidCall(t *testing.T) { - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxInvalidCallReqAppNil(t *testing.T) { - ctx := context.TODO() - pbReq := pbs.PBSRequest{ - App: &openrtb2.App{}, - } - pbBidder := pbs.PBSBidder{} - - _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxInvalidCallMissingSiteID(t *testing.T) { - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Params = json.RawMessage("{}") - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for request with missing siteId") - } -} - -func TestIxTimeout(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil || err != context.DeadlineExceeded { - t.Fatalf("Invalid timeout error received") - } -} - -func TestIxTimeoutMultipleSlots(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - impression := breq.Imp[0] - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - getOpenRTBBid(impression), - }, - }, - }, - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // cancel the request before 2nd impression is returned - // delay to let 1st impression return successfully - if impression.ID == "unitCode2" { - <-time.After(10 * time.Millisecond) - cancel() - <-r.Context().Done() - } - - w.Header().Set("Content-Type", "application/json") - w.Write(js) - }), - ) - defer server.Close() - - pbReq := pbs.PBSRequest{} - - adUnit1 := getAdUnit() - adUnit2 := getAdUnit() - adUnit2.Code = "unitCode2" - adUnit2.Sizes = []openrtb2.Format{ - { - W: 8, - H: 10, - }, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit1, - adUnit2, - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } - - bid := findBidByAdUnitCode(bids, adUnit1.Code) - if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { - t.Fatalf("Received the wrong size") - } -} - -func TestIxInvalidJsonResponse(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Blah") - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxInvalidStatusCode(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 404 - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{IsDebug: true} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxBadRequest(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 400 - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for bad request") - } -} - -func TestIxNoContent(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 204 - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil || bids != nil { - t.Fatalf("Must return nil for no content") - } -} - -func TestIxInvalidCallMissingSize(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Sizes = []openrtb2.Format{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should not have gotten an error for missing/invalid size: %v", err) - } -} - -func TestIxInvalidCallEmptyBidIDResponse(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.BidID = "" - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should have gotten an error for unknown adunit code") - } -} - -func TestIxMismatchUnitCode(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - { - ID: fmt.Sprintf("%d", rand.Intn(1000)), - ImpID: "unitCode_bogus", - Price: 1.0, - AdM: "Content", - W: 10, - H: 12, - }, - }, - }, - }, - } - - 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) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should have gotten an error for unknown adunit code") - } -} - -func TestNoSeatBid(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{} - - 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) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } -} - -func TestNoSeatBidBid(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - {}, - }, - } - - 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) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } -} - -func TestIxInvalidParam(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Params = json.RawMessage("Bogus invalid input") - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should have gotten an error for unrecognized params") - } -} - -func TestIxSingleSlotSingleValidSize(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} - -func TestIxTwoSlotValidSize(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit1 := getAdUnit() - adUnit2 := getVideoAdUnit() - adUnit2.Params = json.RawMessage("{\"siteId\":\"1111\"}") - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit1, - adUnit2, - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 2 { - t.Fatalf("Should have received two bid") - } - - bid := findBidByAdUnitCode(bids, adUnit1.Code) - if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { - t.Fatalf("Received the wrong size") - } - - bid = findBidByAdUnitCode(bids, adUnit2.Code) - if adUnit2.Sizes[0].H != bid.Height || adUnit2.Sizes[0].W != bid.Width { - t.Fatalf("Received the wrong size") - } -} - -func TestIxTwoSlotMultiSizeOnlyValidIXSizeResponse(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Sizes = append(adUnit.Sizes, openrtb2.Format{W: 20, H: 22}) - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 2 { - t.Fatalf("Should have received 2 bids") - } - - for _, size := range adUnit.Sizes { - if !bidResponseForSizeExist(bids, size.H, size.W) { - t.Fatalf("Missing bid for specified size %d and %d", size.W, size.H) - } - } -} - -func bidResponseForSizeExist(bids pbs.PBSBidSlice, h, w int64) bool { - for _, v := range bids { - if v.Height == h && v.Width == w { - return true - } - } - return false -} - -func findBidByAdUnitCode(bids pbs.PBSBidSlice, c string) *pbs.PBSBid { - for _, v := range bids { - if v.AdUnitCode == c { - return v - } - } - return &pbs.PBSBid{} -} - -func TestIxMaxRequests(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - adapter := newAdapter(server.URL) - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnits := []pbs.PBSAdUnit{} - - for i := 0; i < adapter.maxRequests+1; i++ { - adUnits = append(adUnits, getAdUnit()) - } - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: adUnits, - } - - bids, err := adapter.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != adapter.maxRequests { - t.Fatalf("Should have received %d bid", adapter.maxRequests) - } -} - func TestIxMakeBidsWithCategoryDuration(t *testing.T) { bidder := &IxAdapter{} diff --git a/adapters/legacy.go b/adapters/legacy.go deleted file mode 100644 index 8b2221fe0ca..00000000000 --- a/adapters/legacy.go +++ /dev/null @@ -1,97 +0,0 @@ -package adapters - -import ( - "context" - "crypto/tls" - "net/http" - "time" - - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/server/ssl" -) - -// This file contains some deprecated, legacy types. -// -// These support the `/auction` endpoint, but will be replaced by `/openrtb2/auction`. -// New demand partners should ignore this file, and implement the Bidder interface. - -// Adapter is a deprecated interface which connects prebid-server to a demand partner. -// PBS is currently being rewritten to use Bidder, and this will be removed after. -// Their primary purpose is to produce bids in response to Auction requests. -type Adapter interface { - // Name must be identical to the BidderName. - Name() string - // Determines whether this adapter should get callouts if there is not a synched user ID. - SkipNoCookies() bool - // Call produces bids which should be considered, given the auction params. - // - // In practice, implementations almost always make one call to an external server here. - // However, that is not a requirement for satisfying this interface. - // - // An error here will cause all bids to be ignored. If the error was caused by bad user input, - // this should return a BadInputError. If it was caused by bad server behavior - // (e.g. 500, unexpected response format, etc), this should return a BadServerResponseError. - Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) -} - -// HTTPAdapterConfig groups options which control how HTTP requests are made by adapters. -type HTTPAdapterConfig struct { - // See IdleConnTimeout on https://golang.org/pkg/net/http/#Transport - IdleConnTimeout time.Duration - // See MaxIdleConns on https://golang.org/pkg/net/http/#Transport - MaxConns int - // See MaxIdleConnsPerHost on https://golang.org/pkg/net/http/#Transport - MaxConnsPerHost int -} - -type HTTPAdapter struct { - Client *http.Client -} - -// DefaultHTTPAdapterConfig is an HTTPAdapterConfig that chooses sensible default values. -var DefaultHTTPAdapterConfig = &HTTPAdapterConfig{ - MaxConns: 50, - MaxConnsPerHost: 10, - IdleConnTimeout: 60 * time.Second, -} - -// NewHTTPAdapter creates an HTTPAdapter which obeys the rules given by the config, and -// has all the available SSL certs available in the project. -func NewHTTPAdapter(c *HTTPAdapterConfig) *HTTPAdapter { - ts := &http.Transport{ - MaxIdleConns: c.MaxConns, - MaxIdleConnsPerHost: c.MaxConnsPerHost, - IdleConnTimeout: c.IdleConnTimeout, - TLSClientConfig: &tls.Config{RootCAs: ssl.GetRootCAPool()}, - } - - return &HTTPAdapter{ - Client: &http.Client{ - Transport: ts, - }, - } -} - -// used for callOne (possibly pull all of the shared code here) -type CallOneResult struct { - StatusCode int - ResponseBody string - Bid *pbs.PBSBid - Error error -} - -type MisconfiguredAdapter struct { - TheName string - Err error -} - -func (b *MisconfiguredAdapter) Name() string { - return b.TheName -} -func (b *MisconfiguredAdapter) SkipNoCookies() bool { - return false -} - -func (b *MisconfiguredAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - return nil, b.Err -} diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go deleted file mode 100644 index 6aa07c6b764..00000000000 --- a/adapters/openrtb_util.go +++ /dev/null @@ -1,174 +0,0 @@ -package adapters - -import ( - "encoding/json" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/pbs" -) - -func min(x, y int) int { - if x < y { - return x - } - return y -} - -func mediaTypeInSlice(t pbs.MediaType, list []pbs.MediaType) bool { - for _, b := range list { - if b == t { - return true - } - } - return false -} - -func commonMediaTypes(l1 []pbs.MediaType, l2 []pbs.MediaType) []pbs.MediaType { - res := make([]pbs.MediaType, min(len(l1), len(l2))) - i := 0 - for _, b := range l1 { - if mediaTypeInSlice(b, l2) { - res[i] = b - i = i + 1 - } - } - return res[:i] -} - -func makeBanner(unit pbs.PBSAdUnit) *openrtb2.Banner { - return &openrtb2.Banner{ - W: openrtb2.Int64Ptr(unit.Sizes[0].W), - H: openrtb2.Int64Ptr(unit.Sizes[0].H), - Format: copyFormats(unit.Sizes), // defensive copy because adapters may mutate Imps, and this is shared data - TopFrame: unit.TopFrame, - } -} - -func makeVideo(unit pbs.PBSAdUnit) *openrtb2.Video { - // empty mimes array is a sign of uninitialized Video object - if len(unit.Video.Mimes) < 1 { - return nil - } - mimes := make([]string, len(unit.Video.Mimes)) - copy(mimes, unit.Video.Mimes) - pbm := make([]openrtb2.PlaybackMethod, 1) - //this will become int8 soon, so we only care about the first index in the array - pbm[0] = openrtb2.PlaybackMethod(unit.Video.PlaybackMethod) - - protocols := make([]openrtb2.Protocol, 0, len(unit.Video.Protocols)) - for _, protocol := range unit.Video.Protocols { - protocols = append(protocols, openrtb2.Protocol(protocol)) - } - return &openrtb2.Video{ - MIMEs: mimes, - MinDuration: unit.Video.Minduration, - MaxDuration: unit.Video.Maxduration, - W: unit.Sizes[0].W, - H: unit.Sizes[0].H, - StartDelay: openrtb2.StartDelay(unit.Video.Startdelay).Ptr(), - PlaybackMethod: pbm, - Protocols: protocols, - } -} - -// adapters.MakeOpenRTBGeneric makes an openRTB request from the PBS-specific structs. -// -// Any objects pointed to by the returned BidRequest *must not be mutated*, or we will get race conditions. -// The only exception is the Imp property, whose objects will be created new by this method and can be mutated freely. -func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily string, allowedMediatypes []pbs.MediaType) (openrtb2.BidRequest, error) { - imps := make([]openrtb2.Imp, 0, len(bidder.AdUnits)*len(allowedMediatypes)) - for _, unit := range bidder.AdUnits { - if len(unit.Sizes) <= 0 { - continue - } - unitMediaTypes := commonMediaTypes(unit.MediaTypes, allowedMediatypes) - if len(unitMediaTypes) == 0 { - continue - } - - newImp := openrtb2.Imp{ - ID: unit.Code, - Secure: &req.Secure, - Instl: unit.Instl, - } - for _, mType := range unitMediaTypes { - switch mType { - case pbs.MEDIA_TYPE_BANNER: - newImp.Banner = makeBanner(unit) - case pbs.MEDIA_TYPE_VIDEO: - newImp.Video = makeVideo(unit) - // It's strange to error here... but preserves legacy behavior in legacy code. See #603. - if newImp.Video == nil { - return openrtb2.BidRequest{}, &errortypes.BadInput{ - Message: "Invalid AdUnit: VIDEO media type with no video data", - } - } - } - } - if newImp.Banner != nil || newImp.Video != nil { - imps = append(imps, newImp) - } - } - - if len(imps) < 1 { - return openrtb2.BidRequest{}, &errortypes.BadInput{ - Message: "openRTB bids need at least one Imp", - } - } - - if req.App != nil { - return openrtb2.BidRequest{ - ID: req.Tid, - Imp: imps, - App: req.App, - Device: req.Device, - User: req.User, - Source: &openrtb2.Source{ - TID: req.Tid, - }, - AT: 1, - TMax: req.TimeoutMillis, - Regs: req.Regs, - }, nil - } - - buyerUID, _, _ := req.Cookie.GetUID(bidderFamily) - id, _, _ := req.Cookie.GetUID("adnxs") - - var userExt json.RawMessage - if req.User != nil { - userExt = req.User.Ext - } - - return openrtb2.BidRequest{ - ID: req.Tid, - Imp: imps, - Site: &openrtb2.Site{ - Domain: req.Domain, - Page: req.Url, - }, - Device: req.Device, - User: &openrtb2.User{ - BuyerUID: buyerUID, - ID: id, - Ext: userExt, - }, - Source: &openrtb2.Source{ - FD: 1, // upstream, aka header - TID: req.Tid, - }, - AT: 1, - TMax: req.TimeoutMillis, - Regs: req.Regs, - }, nil -} - -func copyFormats(sizes []openrtb2.Format) []openrtb2.Format { - sizesCopy := make([]openrtb2.Format, len(sizes)) - for i := 0; i < len(sizes); i++ { - sizesCopy[i] = sizes[i] - sizesCopy[i].Ext = append([]byte(nil), sizes[i].Ext...) - } - return sizesCopy -} diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go deleted file mode 100644 index 035f4d9b679..00000000000 --- a/adapters/openrtb_util_test.go +++ /dev/null @@ -1,543 +0,0 @@ -package adapters - -import ( - "testing" - - "encoding/json" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - "github.com/stretchr/testify/assert" -) - -func TestCommonMediaTypes(t *testing.T) { - mt1 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - mt2 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - common := commonMediaTypes(mt1, mt2) - assert.Equal(t, len(common), 1) - assert.Equal(t, common[0], pbs.MEDIA_TYPE_BANNER) - - common2 := commonMediaTypes(mt2, mt1) - assert.Equal(t, len(common2), 1) - assert.Equal(t, common2[0], pbs.MEDIA_TYPE_BANNER) - - mt3 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - mt4 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - common3 := commonMediaTypes(mt3, mt4) - assert.Equal(t, len(common3), 2) - - mt5 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - mt6 := []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO} - common4 := commonMediaTypes(mt5, mt6) - assert.Equal(t, len(common4), 0) -} - -func TestOpenRTB(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Instl: 1, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 12) - assert.EqualValues(t, resp.Imp[0].Instl, 1) - - assert.Nil(t, resp.User.Ext) - assert.Nil(t, resp.Regs) -} - -func TestOpenRTBVideo(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}) - - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) - assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) - assert.EqualValues(t, *resp.Imp[0].Video.StartDelay, openrtb2.StartDelay(5)) - assert.EqualValues(t, resp.Imp[0].Video.PlaybackMethod, []openrtb2.PlaybackMethod{openrtb2.PlaybackMethod(1)}) - assert.EqualValues(t, resp.Imp[0].Video.MIMEs, []string{"video/mp4"}) -} - -func TestOpenRTBVideoNoVideoData(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - }, - }, - } - _, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}) - - assert.NotEqual(t, err, nil) - -} - -func TestOpenRTBVideoFilteredOut(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - { - Code: "unitCode2", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - for i := 0; i < len(resp.Imp); i++ { - if resp.Imp[i].Video != nil { - t.Errorf("No video impressions should exist.") - } - } -} - -func TestOpenRTBMultiMediaImp(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, len(resp.Imp), 1) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, resp.Imp[0].Video.W, 10) - assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) - assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) -} - -func TestOpenRTBMultiMediaImpFiltered(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, len(resp.Imp), 1) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, resp.Imp[0].Video, (*openrtb2.Video)(nil)) -} - -func TestOpenRTBNoSize(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - }, - }, - } - _, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - if err == nil { - t.Errorf("Bids without impressions should not be allowed.") - } -} - -func TestOpenRTBMobile(t *testing.T) { - pbReq := pbs.PBSRequest{ - AccountID: "test_account_id", - Tid: "test_tid", - CacheMarkup: 1, - SortBids: 1, - MaxKeyLength: 20, - Secure: 1, - TimeoutMillis: 1000, - App: &openrtb2.App{ - Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb2.Publisher{ - ID: "1995257847363113", - }, - }, - Device: &openrtb2.Device{ - UA: "test_ua", - IP: "test_ip", - Make: "test_make", - Model: "test_model", - IFA: "test_ifa", - }, - User: &openrtb2.User{ - BuyerUID: "test_buyeruid", - }, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 300) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 250) - - assert.EqualValues(t, resp.App.Bundle, "AppNexus.PrebidMobileDemo") - assert.EqualValues(t, resp.App.Publisher.ID, "1995257847363113") - assert.EqualValues(t, resp.User.BuyerUID, "test_buyeruid") - - assert.EqualValues(t, resp.Device.UA, "test_ua") - assert.EqualValues(t, resp.Device.IP, "test_ip") - assert.EqualValues(t, resp.Device.Make, "test_make") - assert.EqualValues(t, resp.Device.Model, "test_model") - assert.EqualValues(t, resp.Device.IFA, "test_ifa") -} - -func TestOpenRTBEmptyUser(t *testing.T) { - pbReq := pbs.PBSRequest{ - User: &openrtb2.User{}, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode2", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.EqualValues(t, resp.User, &openrtb2.User{}) -} - -func TestOpenRTBUserWithCookie(t *testing.T) { - pbsCookie := usersync.NewCookie() - pbsCookie.TrySync("test", "abcde") - pbReq := pbs.PBSRequest{ - User: &openrtb2.User{}, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - }, - }, - } - pbReq.Cookie = pbsCookie - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.EqualValues(t, resp.User.BuyerUID, "abcde") -} - -func TestSizesCopy(t *testing.T) { - formats := []openrtb2.Format{ - { - W: 10, - }, - { - Ext: []byte{0x5}, - }, - } - clone := copyFormats(formats) - - if len(clone) != 2 { - t.Error("The copy should have 2 elements") - } - if clone[0].W != 10 { - t.Error("The Format's width should be preserved.") - } - if len(clone[1].Ext) != 1 || clone[1].Ext[0] != 0x5 { - t.Error("The Format's Ext should be preserved.") - } - if &formats[0] == &clone[0] || &formats[1] == &clone[1] { - t.Error("The Format elements should not point to the same instance") - } - if &formats[0] == &clone[0] || &formats[1] == &clone[1] { - t.Error("The Format elements should not point to the same instance") - } - if &formats[1].Ext[0] == &clone[1].Ext[0] { - t.Error("The Format.Ext property should point to two different instances") - } -} - -func TestMakeVideo(t *testing.T) { - adUnit := pbs.PBSAdUnit{ - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{1, 2, 5, 6}, - }, - } - video := makeVideo(adUnit) - assert.EqualValues(t, video.MinDuration, 15) - assert.EqualValues(t, video.MaxDuration, 30) - assert.EqualValues(t, *video.StartDelay, openrtb2.StartDelay(5)) - assert.EqualValues(t, len(video.PlaybackMethod), 1) - assert.EqualValues(t, len(video.Protocols), 4) -} - -func TestGDPR(t *testing.T) { - - rawUserExt := json.RawMessage(`{"consent": "12345"}`) - userExt, _ := json.Marshal(rawUserExt) - - rawRegsExt := json.RawMessage(`{"gdpr": 1}`) - regsExt, _ := json.Marshal(rawRegsExt) - - pbReq := pbs.PBSRequest{ - User: &openrtb2.User{ - Ext: userExt, - }, - Regs: &openrtb2.Regs{ - Ext: regsExt, - }, - } - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Instl: 1, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 12) - assert.EqualValues(t, resp.Imp[0].Instl, 1) - - assert.EqualValues(t, resp.User.Ext, userExt) - assert.EqualValues(t, resp.Regs.Ext, regsExt) -} - -func TestGDPRMobile(t *testing.T) { - rawUserExt := json.RawMessage(`{"consent": "12345"}`) - userExt, _ := json.Marshal(rawUserExt) - - rawRegsExt := json.RawMessage(`{"gdpr": 1}`) - regsExt, _ := json.Marshal(rawRegsExt) - - pbReq := pbs.PBSRequest{ - AccountID: "test_account_id", - Tid: "test_tid", - CacheMarkup: 1, - SortBids: 1, - MaxKeyLength: 20, - Secure: 1, - TimeoutMillis: 1000, - App: &openrtb2.App{ - Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb2.Publisher{ - ID: "1995257847363113", - }, - }, - Device: &openrtb2.Device{ - UA: "test_ua", - IP: "test_ip", - Make: "test_make", - Model: "test_model", - IFA: "test_ifa", - }, - User: &openrtb2.User{ - BuyerUID: "test_buyeruid", - Ext: userExt, - }, - Regs: &openrtb2.Regs{ - Ext: regsExt, - }, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 300) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 250) - - assert.EqualValues(t, resp.App.Bundle, "AppNexus.PrebidMobileDemo") - assert.EqualValues(t, resp.App.Publisher.ID, "1995257847363113") - assert.EqualValues(t, resp.User.BuyerUID, "test_buyeruid") - - assert.EqualValues(t, resp.Device.UA, "test_ua") - assert.EqualValues(t, resp.Device.IP, "test_ip") - assert.EqualValues(t, resp.Device.Make, "test_make") - assert.EqualValues(t, resp.Device.Model, "test_model") - assert.EqualValues(t, resp.Device.IFA, "test_ifa") - - assert.EqualValues(t, resp.User.Ext, userExt) - assert.EqualValues(t, resp.Regs.Ext, regsExt) -} diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index c2e9fffa0fe..19024f4a123 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -1,11 +1,8 @@ package pubmatic import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "strconv" "strings" @@ -16,46 +13,23 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" ) const MAX_IMPRESSIONS_PUBMATIC = 30 type PubmaticAdapter struct { - http *adapters.HTTPAdapter - URI string + URI string } -// used for cookies and such -func (a *PubmaticAdapter) Name() string { - return "pubmatic" -} - -func (a *PubmaticAdapter) SkipNoCookies() bool { - return false -} - -// Below is bidder specific parameters for pubmatic adaptor, -// PublisherId and adSlot are mandatory parameters, others are optional parameters -// Keywords is bid specific parameter, -// WrapExt needs to be sent once per bid request -type pubmaticParams struct { - PublisherId string `json:"publisherId"` - AdSlot string `json:"adSlot"` - WrapExt json.RawMessage `json:"wrapper,omitempty"` - Keywords map[string]string `json:"keywords,omitempty"` +type pubmaticBidExt struct { + BidType *int `json:"BidType,omitempty"` + VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` } type pubmaticBidExtVideo struct { Duration *int `json:"duration,omitempty"` } -type pubmaticBidExt struct { - BidType *int `json:"BidType,omitempty"` - VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` -} - type ExtImpBidderPubmatic struct { adapters.ExtImpBidder Data *ExtData `json:"data,omitempty"` @@ -72,16 +46,6 @@ type ExtAdServer struct { } const ( - INVALID_PARAMS = "Invalid BidParam" - MISSING_PUBID = "Missing PubID" - MISSING_ADSLOT = "Missing AdSlot" - INVALID_WRAPEXT = "Invalid WrapperExt" - INVALID_ADSIZE = "Invalid AdSize" - INVALID_WIDTH = "Invalid Width" - INVALID_HEIGHT = "Invalid Height" - INVALID_MEDIATYPE = "Invalid MediaType" - INVALID_ADSLOT = "Invalid AdSlot" - dctrKeyName = "key_val" pmZoneIDKeyName = "pmZoneId" pmZoneIDKeyNameOld = "pmZoneID" @@ -89,251 +53,6 @@ const ( AdServerGAM = "gam" ) -func PrepareLogMessage(tID, pubId, adUnitId, bidID, details string, args ...interface{}) string { - return fmt.Sprintf("[PUBMATIC] ReqID [%s] PubID [%s] AdUnit [%s] BidID [%s] %s \n", - tID, pubId, adUnitId, bidID, details) -} - -func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - pbReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - logf("[PUBMATIC] Failed to make ortb request for request id [%s] \n", pbReq.ID) - return nil, err - } - - var errState []string - adSlotFlag := false - pubId := "" - wrapExt := "" - if len(bidder.AdUnits) > MAX_IMPRESSIONS_PUBMATIC { - logf("[PUBMATIC] First %d impressions will be considered from request tid %s\n", - MAX_IMPRESSIONS_PUBMATIC, pbReq.ID) - } - - for i, unit := range bidder.AdUnits { - var params pubmaticParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_PARAMS, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid JSON [%s] err [%s]", unit.Params, err.Error()))) - continue - } - - if params.PublisherId == "" { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, MISSING_PUBID, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: Publisher Id missing"))) - continue - } - pubId = params.PublisherId - - if params.AdSlot == "" { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, MISSING_ADSLOT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: adSlot missing"))) - continue - } - - // Parse Wrapper Extension i.e. ProfileID and VersionID only once per request - if wrapExt == "" && len(params.WrapExt) != 0 { - var wrapExtMap map[string]int - err := json.Unmarshal([]byte(params.WrapExt), &wrapExtMap) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_WRAPEXT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: Wrapper Extension Invalid"))) - continue - } - wrapExt = string(params.WrapExt) - } - - adSlotStr := strings.TrimSpace(params.AdSlot) - adSlot := strings.Split(adSlotStr, "@") - if len(adSlot) == 2 && adSlot[0] != "" && adSlot[1] != "" { - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(pbReq.Imp) <= i { - break - } - if pbReq.Imp[i].Banner != nil { - adSize := strings.Split(strings.ToLower(strings.TrimSpace(adSlot[1])), "x") - if len(adSize) == 2 { - width, err := strconv.Atoi(strings.TrimSpace(adSize[0])) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_WIDTH, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSlot width [%s]", adSize[0]))) - continue - } - - heightStr := strings.Split(strings.TrimSpace(adSize[1]), ":") - height, err := strconv.Atoi(strings.TrimSpace(heightStr[0])) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_HEIGHT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSlot height [%s]", heightStr[0]))) - continue - } - - pbReq.Imp[i].TagID = strings.TrimSpace(adSlot[0]) - 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) - pbReq.Imp[i].Ext = json.RawMessage([]byte(kvstr)) - } else { - pbReq.Imp[i].Ext = nil - } - - adSlotFlag = true - } else { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_ADSIZE, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSize [%s]", adSize))) - continue - } - } else { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_MEDIATYPE, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid Media Type"))) - continue - } - } else { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_ADSLOT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSlot [%s]", params.AdSlot))) - continue - } - - if pbReq.Site != nil { - siteCopy := *pbReq.Site - siteCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} - pbReq.Site = &siteCopy - } - if pbReq.App != nil { - appCopy := *pbReq.App - appCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} - pbReq.App = &appCopy - } - } - - if !(adSlotFlag) { - return nil, &errortypes.BadInput{ - Message: "Incorrect adSlot / Publisher params, Error list: [" + strings.Join(errState, ",") + "]", - } - } - - if wrapExt != "" { - rawExt := fmt.Sprintf("{\"wrapper\": %s}", wrapExt) - pbReq.Ext = json.RawMessage(rawExt) - } - - reqJSON, err := json.Marshal(pbReq) - - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - } - - userId, _, _ := req.Cookie.GetUID(a.Name()) - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON)) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - httpReq.AddCookie(&http.Cookie{ - Name: "KADUSERCOOKIE", - Value: userId, - }) - - pbResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - debug.StatusCode = pbResp.StatusCode - - if pbResp.StatusCode == http.StatusNoContent { - return nil, nil - } - - if pbResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), - } - } - - if pbResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), - } - } - - defer pbResp.Body.Close() - body, err := ioutil.ReadAll(pbResp.Body) - if err != nil { - return nil, err - } - - if req.IsDebug { - debug.ResponseBody = string(body) - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - numBids := 0 - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - numBids++ - - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - - var bidExt pubmaticBidExt - mediaType := openrtb_ext.BidTypeBanner - if err := json.Unmarshal(bid.Ext, &bidExt); err == nil { - mediaType = getBidType(&bidExt) - } - pbid.CreativeMediaType = string(mediaType) - - bids = append(bids, &pbid) - logf("[PUBMATIC] Returned Bid for PubID [%s] AdUnit [%s] BidID [%s] Size [%dx%d] Price [%f] \n", - pubId, pbid.AdUnitCode, pbid.BidID, pbid.Width, pbid.Height, pbid.Price) - } - } - - return bids, nil -} - func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) @@ -535,7 +254,6 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er } return nil - } func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) { @@ -553,22 +271,6 @@ func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[s } } -func prepareImpressionExt(keywords map[string]string) string { - - eachKv := make([]string, 0, len(keywords)) - for key, val := range keywords { - if len(val) == 0 { - logf("No values present for key = %s", key) - continue - } else { - eachKv = append(eachKv, fmt.Sprintf("\"%s\":\"%s\"", key, val)) - } - } - - kvStr := "{" + strings.Join(eachKv, ",") + "}" - return kvStr -} - func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -646,15 +348,6 @@ func logf(msg string, args ...interface{}) { } } -func NewPubmaticLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PubmaticAdapter { - a := adapters.NewHTTPAdapter(config) - - return &PubmaticAdapter{ - http: a, - URI: uri, - } -} - // Builder builds a new instance of the Pubmatic adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &PubmaticAdapter{ diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 2e8a6804850..ac7dbdb711f 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -1,25 +1,11 @@ package pubmatic import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/http/httptest" "testing" - "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" ) func TestJsonSamples(t *testing.T) { @@ -33,655 +19,8 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "pubmatictest", bidder) } -// ---------------------------------------------------------------------------- -// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb2. - -func CompareStringValue(val1 string, val2 string, t *testing.T) { - if val1 != val2 { - t.Fatalf(fmt.Sprintf("Expected = %s , Actual = %s", val2, val1)) - } -} - -func DummyPubMaticServer(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 - } - - resp := openrtb2.BidResponse{ - ID: breq.ID, - BidID: "bidResponse_ID", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "pubmatic", - Bid: make([]openrtb2.Bid, 0), - }, - }, - } - rand.Seed(int64(time.Now().UnixNano())) - var bids []openrtb2.Bid - - for i, imp := range breq.Imp { - bids = append(bids, openrtb2.Bid{ - ID: fmt.Sprintf("SeatID_%d", i), - ImpID: imp.ID, - Price: float64(int(rand.Float64()*1000)) / 100, - AdID: fmt.Sprintf("adID-%d", i), - AdM: "AdContent", - CrID: fmt.Sprintf("creative-%d", i), - W: *imp.Banner.W, - H: *imp.Banner.H, - DealID: fmt.Sprintf("DealID_%d", i), - }) - } - resp.SeatBid[0].Bid = bids - - 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 TestPubmaticInvalidCall(t *testing.T) { - - an := NewPubmaticLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "blah") - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestPubmaticTimeout(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), - }, - }, - } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil || err != context.DeadlineExceeded { - t.Fatalf("No timeout received for timed out request: %v", err) - } -} - -func TestPubmaticInvalidJson(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Blah") - }), - ) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), - }, - }, - } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestPubmaticInvalidStatusCode(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 404 - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), - }, - }, - } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestPubmaticInvalidInputParameters(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - }, - }, - } - - pbReq.IsDebug = true - inValidPubmaticParams := []json.RawMessage{ - // Invalid Request JSON - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\""), - // Missing adSlot in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\"}"), - // Missing publisher ID - json.RawMessage("{\"adSlot\": \"slot@120x240\"}"), - // Missing slot name in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"@120x240\"}"), - // Invalid adSize in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120-240\"}"), - // Missing impression width and height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@\"}"), - // Missing height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120\"}"), - // Missing width in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@x120\"}"), - // Incorrect width param in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@valx120\"}"), - // Incorrect height param in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120xval\"}"), - // Empty slot name in AdUnits.Params, - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" @120x240\"}"), - // Empty width in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@ x240\"}"), - // Empty height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x \"}"), - // Empty height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" @120x \"}"), - // Invalid Keywords - json.RawMessage(`{"publisherId": "640", "adSlot": "slot1@336x280","keywords":{"pmZoneId":1},"wrapper":{"version":2,"profile":595}}`), - // Invalid Wrapper ext - json.RawMessage(`{"publisherId": "640", "adSlot": "slot1@336x280","keywords":{"pmZoneId":"Zone1,Zone2"},"wrapper":{"version":"2","profile":595}}`), - } - - for _, param := range inValidPubmaticParams { - pbBidder.AdUnits[0].Params = param - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("Should get errors for params = %v", string(param)) - } - } - -} - -func TestPubmaticBasicResponse_MandatoryParams(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - }, - } - pbReq.IsDebug = true - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} - -func TestPubmaticBasicResponse_AllParams(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage(`{"publisherId": "640", - "adSlot": "slot1@336x280", - "keywords":{ - "pmZoneId": "Zone1,Zone2" - }, - "wrapper": - {"version":2, - "profile":595} - }`), - }, - }, - } - pbReq.IsDebug = true - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} - -func TestPubmaticMultiImpressionResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode1", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - { - Code: "unitCode1", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 800, - H: 200, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@800x200\"}"), - }, - }, - } - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 2 { - t.Fatalf("Should have received two bids") - } -} - -func TestPubmaticMultiAdUnitResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode1", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - { - Code: "unitCode2", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 800, - H: 200, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@800x200\"}"), - }, - }, - } - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 2 { - t.Fatalf("Should have received one bid") - } - -} - -func TestPubmaticMobileResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - }, - } - - pbReq.App = &openrtb2.App{ - ID: "com.test", - Name: "testApp", - } - - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} -func TestPubmaticInvalidLookupBidIDParameter(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - }, - }, - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}") - _, err := an.Call(ctx, &pbReq, &pbBidder) - - CompareStringValue(err.Error(), "Unknown ad unit code 'unitCode'", t) -} - -func TestPubmaticAdSlotParams(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - }, - }, - } - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" slot@120x240\"}") - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot @120x240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240 \"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@ 120x240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@220 x240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x 240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240:1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x 240:1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240 :1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240: 1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } -} - -func TestPubmaticSampleRequest(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - pbReq := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 1), - } - pbReq.AdUnits[0] = pbs.AdUnit{ - Code: "adUnit_1", - Sizes: []openrtb2.Format{ - { - W: 100, - H: 120, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "pubmatic", - BidID: "BidID", - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@100x120\"}"), - }, - }, - } - - pbReq.IsDebug = true - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbReq) - if err != nil { - t.Fatalf("Error when serializing request") - } - - httpReq := httptest.NewRequest("POST", server.URL, body) - httpReq.Header.Add("Referer", "http://test.com/sports") - pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) - pc.TrySync("pubmatic", "12345") - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - hcs := config.HostCookie{} - - _, err = pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcs) - if err != nil { - t.Fatalf("Error when parsing request: %v", err) - } -} - func TestGetBidTypeVideo(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 1 actualBidTypeValue := getBidType(pubmaticExt) @@ -691,8 +30,8 @@ func TestGetBidTypeVideo(t *testing.T) { } func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { - pubmaticExt := pubmaticBidExt{} - actualBidTypeValue := getBidType(&pubmaticExt) + pubmaticExt := &pubmaticBidExt{} + actualBidTypeValue := getBidType(pubmaticExt) // banner is the default bid type when no bidType key is present in the bid.ext if actualBidTypeValue != "banner" { t.Errorf("Expected Bid Type value was: banner, actual value is: %v", actualBidTypeValue) @@ -700,7 +39,7 @@ func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { } func TestGetBidTypeBanner(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 0 actualBidTypeValue := getBidType(pubmaticExt) @@ -710,7 +49,7 @@ func TestGetBidTypeBanner(t *testing.T) { } func TestGetBidTypeNative(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 2 actualBidTypeValue := getBidType(pubmaticExt) @@ -720,7 +59,7 @@ func TestGetBidTypeNative(t *testing.T) { } func TestGetBidTypeForUnsupportedCode(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 99 actualBidTypeValue := getBidType(pubmaticExt) diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 6b6b4305607..f756d5dd31a 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -1,27 +1,21 @@ package pulsepoint import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "strconv" - "strings" - "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" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type PulsePointAdapter struct { - http *adapters.HTTPAdapter - URI string + URI string } // Builds an instance of PulsePointAdapter @@ -168,192 +162,3 @@ func getBidType(imp openrtb2.Imp) openrtb_ext.BidType { } return "" } - -///////////////////////////////// -// Legacy implementation: Start -///////////////////////////////// - -func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter { - a := adapters.NewHTTPAdapter(config) - - return &PulsePointAdapter{ - http: a, - URI: uri, - } -} - -// used for cookies and such -func (a *PulsePointAdapter) Name() string { - return "pulsepoint" -} - -// parameters for pulsepoint adapter. -type PulsepointParams struct { - PublisherId int `json:"cp"` - TagId int `json:"ct"` - AdSize string `json:"cf"` -} - -func (a *PulsePointAdapter) SkipNoCookies() bool { - return false -} - -func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - ppReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - return nil, err - } - - for i, unit := range bidder.AdUnits { - var params PulsepointParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - if params.PublisherId == 0 { - return nil, &errortypes.BadInput{ - Message: "Missing PublisherId param cp", - } - } - if params.TagId == 0 { - return nil, &errortypes.BadInput{ - Message: "Missing TagId param ct", - } - } - if params.AdSize == "" { - return nil, &errortypes.BadInput{ - Message: "Missing AdSize param cf", - } - } - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(ppReq.Imp) <= i { - break - } - ppReq.Imp[i].TagID = strconv.Itoa(params.TagId) - publisher := &openrtb2.Publisher{ID: strconv.Itoa(params.PublisherId)} - if ppReq.Site != nil { - siteCopy := *ppReq.Site - siteCopy.Publisher = publisher - ppReq.Site = &siteCopy - } else { - appCopy := *ppReq.App - appCopy.Publisher = publisher - ppReq.App = &appCopy - } - if ppReq.Imp[i].Banner != nil { - var size = strings.Split(strings.ToLower(params.AdSize), "x") - if len(size) == 2 { - width, err := strconv.Atoi(size[0]) - if err == nil { - ppReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) - } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid Width param %s", size[0]), - } - } - height, err := strconv.Atoi(size[1]) - if err == nil { - ppReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) - } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid Height param %s", size[1]), - } - } - } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid AdSize param %s", params.AdSize), - } - } - } - } - reqJSON, err := json.Marshal(ppReq) - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON)) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - ppResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - debug.StatusCode = ppResp.StatusCode - - if ppResp.StatusCode == http.StatusNoContent { - return nil, nil - } - - if ppResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d", ppResp.StatusCode), - } - } - - if ppResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", ppResp.StatusCode), - } - } - - defer ppResp.Body.Close() - body, err := ioutil.ReadAll(ppResp.Body) - if err != nil { - return nil, err - } - - if req.IsDebug { - debug.ResponseBody = string(body) - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - CreativeMediaType: string(openrtb_ext.BidTypeBanner), - } - bids = append(bids, &pbid) - } - } - - return bids, nil -} - -///////////////////////////////// -// Legacy implementation: End -///////////////////////////////// diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index a4e20b04859..8929898522a 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -1,26 +1,11 @@ package pulsepoint import ( - "encoding/json" - "net/http" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" - - "bytes" - "context" - "fmt" - "io/ioutil" - "net/http/httptest" - "time" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestJsonSamples(t *testing.T) { @@ -33,280 +18,3 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "pulsepointtest", bidder) } - -///////////////////////////////// -// Legacy implementation: Start -///////////////////////////////// - -/** - * Verify adapter names are setup correctly. - */ -func TestPulsePointAdapterNames(t *testing.T) { - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://localhost/bid") - adapterstest.VerifyStringValue(adapter.Name(), "pulsepoint", t) -} - -/** - * Test required parameters not sent - */ -func TestPulsePointRequiredBidParameters(t *testing.T) { - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://localhost/bid") - ctx := context.TODO() - req := SampleRequest(1, t) - bidder := req.Bidders[0] - // remove "ct" param and verify error message. - bidder.AdUnits[0].Params = json.RawMessage("{\"cp\": 2001, \"cf\": \"728X90\"}") - _, errTag := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errTag.Error(), "Missing TagId param ct", t) - // remove "cp" param and verify error message. - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cf\": \"728X90\"}") - _, errPub := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errPub.Error(), "Missing PublisherId param cp", t) - // remove "cf" param and verify error message. - bidder.AdUnits[0].Params = json.RawMessage("{\"cp\": 2001, \"ct\": 1001}") - _, errSize := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errSize.Error(), "Missing AdSize param cf", t) - // invalid width parameter value for cf - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"aXb\"}") - _, errWidth := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errWidth.Error(), "Invalid Width param a", t) - // invalid parameter values for cf - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"12Xb\"}") - _, errHeight := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errHeight.Error(), "Invalid Height param b", t) - // invalid parameter values for cf - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"12-20\"}") - _, errAdSizeValue := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errAdSizeValue.Error(), "Invalid AdSize param 12-20", t) -} - -/** - * Verify the openrtb request sent to Pulsepoint endpoint. - * Ensure the ct, cp, cf params are transformed and sent alright. - */ -func TestPulsePointOpenRTBRequest(t *testing.T) { - service := CreateService(adapterstest.BidOnTags("")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(1, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - adapter.Call(ctx, req, bidder) - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) - adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "1001", t) - adapterstest.VerifyStringValue(service.LastBidRequest.Site.Publisher.ID, "2001", t) - adapterstest.VerifyBannerSize(service.LastBidRequest.Imp[0].Banner, 728, 90, t) -} - -/** - * Verify bidding behavior. - */ -func TestPulsePointBiddingBehavior(t *testing.T) { - // setup server endpoint to return bid. - server := CreateService(adapterstest.BidOnTags("1001")).Server - ctx := context.TODO() - req := SampleRequest(1, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // number of bids should be 1 - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[0].BidderCode, "pulsepoint", t) - adapterstest.VerifyStringValue(bids[0].Adm, "
This is an Ad
", t) - adapterstest.VerifyStringValue(bids[0].Creative_id, "Cr-234", t) - adapterstest.VerifyIntValue(int(bids[0].Width), 728, t) - adapterstest.VerifyIntValue(int(bids[0].Height), 90, t) - adapterstest.VerifyIntValue(int(bids[0].Price*100), 210, t) - adapterstest.VerifyStringValue(bids[0].CreativeMediaType, string(openrtb_ext.BidTypeBanner), t) -} - -/** - * Verify bidding behavior on multiple impressions, some impressions make a bid - */ -func TestPulsePointMultiImpPartialBidding(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("1001")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(2, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) -} - -/** - * Verify bidding behavior on multiple impressions, all impressions passed back. - */ -func TestPulsePointMultiImpPassback(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(2, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 0, t) -} - -/** - * Verify bidding behavior on multiple impressions, all impressions passed back. - */ -func TestPulsePointMultiImpAllBid(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("1001,1002")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(2, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 2, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[1].AdUnitCode, "div-adunit-2", t) -} - -/** - * Verify bidding behavior on mobile app requests - */ -func TestMobileAppRequest(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("1001")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(1, t) - req.App = &openrtb2.App{ - ID: "com.facebook.katana", - Name: "facebook", - } - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // one mobile app impression sent. - // verify appropriate fields are sent to pulsepoint endpoint. - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) - adapterstest.VerifyStringValue(service.LastBidRequest.App.ID, "com.facebook.katana", t) - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) -} - -/** - * Produces a sample PBSRequest, for the impressions given. - */ -func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { - // create a request object - req := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 2), - } - req.AccountID = "1" - tagId := 1001 - for i := 0; i < numberOfImpressions; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "pulsepoint", - BidID: fmt.Sprintf("Bid-%d", i+1), - Params: json.RawMessage(fmt.Sprintf("{\"ct\": %d, \"cp\": 2001, \"cf\": \"728X90\"}", tagId+i)), - }, - }, - } - } - // serialize the request to json - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(req) - if err != nil { - t.Fatalf("Error when serializing request") - } - // setup a http request - httpReq := httptest.NewRequest("POST", CreateService(adapterstest.BidOnTags("")).Server.URL, body) - httpReq.Header.Add("Referer", "http://news.pub/topnews") - pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) - pc.TrySync("pulsepoint", "pulsepointUser123") - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - // parse the http request - cacheClient, _ := dummycache.New() - hcs := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcs) - if err != nil { - t.Fatalf("Error when parsing request: %v", err) - } - return parsedReq -} - -/** - * Represents a mock ORTB endpoint of PulsePoint. Would return a bid - * for TagId 1001 and passback for 1002 as the default behavior. - */ -func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { - service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb2.BidRequest - server := httptest.NewServer(http.HandlerFunc(func(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 - } - lastBidRequest = breq - var bids []openrtb2.Bid - for i, imp := range breq.Imp { - if tagsToBid[imp.TagID] { - bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) - } - } - // no bids were produced, pulsepoint service returns 204 - if len(bids) == 0 { - w.WriteHeader(204) - } else { - // serialize the bids to openrtb2.BidResponse - js, _ := json.Marshal(openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: bids, - }, - }, - }) - w.Header().Set("Content-Type", "application/json") - w.Write(js) - } - })) - service.Server = server - service.LastBidRequest = &lastBidRequest - return service -} - -///////////////////////////////// -// Legacy implementation: End -///////////////////////////////// diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 80c62df16a1..ace1bfaa12d 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -1,53 +1,30 @@ package rubicon import ( - "bytes" - "context" "encoding/json" "fmt" - "github.com/buger/jsonparser" - "io/ioutil" "net/http" "net/url" "strconv" "strings" - "github.com/golang/glog" - "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" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const badvLimitSize = 50 type RubiconAdapter struct { - http *adapters.HTTPAdapter URI string XAPIUsername string XAPIPassword string } -func (a *RubiconAdapter) Name() string { - return "rubicon" -} - -func (a *RubiconAdapter) SkipNoCookies() bool { - return false -} - -type rubiconParams struct { - AccountId int `json:"accountId"` - SiteId int `json:"siteId"` - ZoneId int `json:"zoneId"` - Inventory json.RawMessage `json:"inventory,omitempty"` - Visitor json.RawMessage `json:"visitor,omitempty"` - Video rubiconVideoParams `json:"video"` -} - type bidRequestExt struct { Prebid bidRequestExtPrebid `json:"prebid"` } @@ -134,15 +111,6 @@ type rubiconBannerExt struct { } // ***** Video Extension ***** -type rubiconVideoParams struct { - Language string `json:"language,omitempty"` - PlayerHeight int `json:"playerHeight,omitempty"` - PlayerWidth int `json:"playerWidth,omitempty"` - VideoSizeID int `json:"size_id,omitempty"` - Skip int `json:"skip,omitempty"` - SkipDelay int `json:"skipdelay,omitempty"` -} - type rubiconVideoExt struct { Skip int `json:"skip,omitempty"` SkipDelay int `json:"skipdelay,omitempty"` @@ -154,19 +122,6 @@ type rubiconVideoExtRP struct { SizeID int `json:"size_id,omitempty"` } -type rubiconTargetingExt struct { - RP rubiconTargetingExtRP `json:"rp"` -} - -type rubiconTargetingExtRP struct { - Targeting []rubiconTargetingObj `json:"targeting"` -} - -type rubiconTargetingObj struct { - Key string `json:"key"` - Values []string `json:"values"` -} - type rubiconDeviceExtRP struct { PixelRatio float64 `json:"pixelratio"` } @@ -175,10 +130,6 @@ type rubiconDeviceExt struct { RP rubiconDeviceExtRP `json:"rp"` } -type rubiconUser struct { - Language string `json:"language"` -} - type rubiconBidResponse struct { openrtb2.BidResponse SeatBid []rubiconSeatBid `json:"seatbid,omitempty"` @@ -353,273 +304,6 @@ func parseRubiconSizes(sizes []openrtb2.Format) (primary int, alt []int, err err return } -func (a *RubiconAdapter) callOne(ctx context.Context, 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") - httpReq.Header.Add("User-Agent", "prebid-server/1.0") - httpReq.SetBasicAuth(a.XAPIUsername, a.XAPIPassword) - - rubiResp, e := ctxhttp.Do(ctx, a.http.Client, httpReq) - if e != nil { - err = e - return - } - - defer rubiResp.Body.Close() - body, _ := ioutil.ReadAll(rubiResp.Body) - result.ResponseBody = string(body) - - result.StatusCode = rubiResp.StatusCode - - if rubiResp.StatusCode == 204 { - return - } - - if rubiResp.StatusCode == http.StatusBadRequest { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), - } - } - - if rubiResp.StatusCode != http.StatusOK { - err = &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), - } - return - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - err = &errortypes.BadServerResponse{ - Message: err.Error(), - } - return - } - if len(bidResp.SeatBid) == 0 { - return - } - if len(bidResp.SeatBid[0].Bid) == 0 { - return - } - bid := bidResp.SeatBid[0].Bid[0] - - result.Bid = &pbs.PBSBid{ - AdUnitCode: bid.ImpID, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - // for video, the width and height are undefined as there's no corresponding return value from XAPI - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - - // Pull out any server-side determined targeting - var rpExtTrg rubiconTargetingExt - - if err := json.Unmarshal([]byte(bid.Ext), &rpExtTrg); err == nil { - // Converting string => array(string) to string => string - targeting := make(map[string]string) - - // Only pick off the first for now - for _, target := range rpExtTrg.RP.Targeting { - targeting[target.Key] = target.Values[0] - } - - result.Bid.AdServerTargeting = targeting - } - - return -} - -type callOneObject struct { - requestJson bytes.Buffer - mediaType pbs.MediaType -} - -func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - callOneObjects := make([]callOneObject, 0, len(bidder.AdUnits)) - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - - rubiReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), supportedMediaTypes) - if err != nil { - return nil, err - } - - rubiReqImpCopy := rubiReq.Imp - - for i, unit := range bidder.AdUnits { - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(rubiReqImpCopy) <= i { - break - } - // Only grab this ad unit - // Not supporting multi-media-type add-unit yet - thisImp := rubiReqImpCopy[i] - - // Amend it with RP-specific information - var params rubiconParams - err = json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - - var mint, mintVersion string - mint = "prebid" - mintVersion = req.SDK.Source + "_" + req.SDK.Platform + "_" + req.SDK.Version - track := rubiconImpExtRPTrack{Mint: mint, MintVersion: mintVersion} - - impExt := rubiconImpExt{RP: rubiconImpExtRP{ - ZoneID: params.ZoneId, - Target: params.Inventory, - Track: track, - }} - thisImp.Ext, err = json.Marshal(&impExt) - if err != nil { - continue - } - - // Copy the $.user object and amend with $.user.ext.rp.target - // Copy avoids race condition since it points to ref & shared with other adapters - userCopy := *rubiReq.User - userExt := rubiconUserExt{RP: rubiconUserExtRP{Target: params.Visitor}} - userCopy.Ext, err = json.Marshal(&userExt) - // Assign back our copy - rubiReq.User = &userCopy - - deviceCopy := *rubiReq.Device - deviceExt := rubiconDeviceExt{RP: rubiconDeviceExtRP{PixelRatio: rubiReq.Device.PxRatio}} - deviceCopy.Ext, err = json.Marshal(&deviceExt) - rubiReq.Device = &deviceCopy - - if thisImp.Video != nil { - - videoSizeId := params.Video.VideoSizeID - if videoSizeId == 0 { - resolvedSizeId, err := resolveVideoSizeId(thisImp.Video.Placement, thisImp.Instl, thisImp.ID) - if err == nil { - videoSizeId = resolvedSizeId - } else { - continue - } - } - - videoExt := rubiconVideoExt{Skip: params.Video.Skip, SkipDelay: params.Video.SkipDelay, RP: rubiconVideoExtRP{SizeID: videoSizeId}} - thisImp.Video.Ext, err = json.Marshal(&videoExt) - } else { - primarySizeID, altSizeIDs, err := parseRubiconSizes(unit.Sizes) - if err != nil { - continue - } - bannerExt := rubiconBannerExt{RP: rubiconBannerExtRP{SizeID: primarySizeID, AltSizeIDs: altSizeIDs, MIME: "text/html"}} - thisImp.Banner.Ext, err = json.Marshal(&bannerExt) - } - - siteExt := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: params.SiteId}} - pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: params.AccountId}} - var rubiconUser rubiconUser - err = json.Unmarshal(req.PBSUser, &rubiconUser) - - if rubiReq.Site != nil { - siteCopy := *rubiReq.Site - siteCopy.Ext, err = json.Marshal(&siteExt) - siteCopy.Publisher = &openrtb2.Publisher{} - siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) - siteCopy.Content = &openrtb2.Content{} - siteCopy.Content.Language = rubiconUser.Language - rubiReq.Site = &siteCopy - } else { - site := &openrtb2.Site{} - site.Content = &openrtb2.Content{} - site.Content.Language = rubiconUser.Language - rubiReq.Site = site - } - - if rubiReq.App != nil { - appCopy := *rubiReq.App - appCopy.Ext, err = json.Marshal(&siteExt) - appCopy.Publisher = &openrtb2.Publisher{} - appCopy.Publisher.Ext, err = json.Marshal(&pubExt) - rubiReq.App = &appCopy - } - - rubiReq.Imp = []openrtb2.Imp{thisImp} - - var reqBuffer bytes.Buffer - err = json.NewEncoder(&reqBuffer).Encode(rubiReq) - if err != nil { - return nil, err - } - callOneObjects = append(callOneObjects, callOneObject{reqBuffer, unit.MediaTypes[0]}) - } - if len(callOneObjects) == 0 { - return nil, &errortypes.BadInput{ - Message: "Invalid ad unit/imp", - } - } - - ch := make(chan adapters.CallOneResult) - for _, obj := range callOneObjects { - go func(bidder *pbs.PBSBidder, reqJSON bytes.Buffer, mediaType pbs.MediaType) { - result, err := a.callOne(ctx, 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 - } else { - // no need to check whether mediaTypes is nil or length of zero, pbs.ParsePBSRequest will cover - // these cases. - // for media types other than banner and video, pbs.ParseMediaType will throw error. - // we may want to create a map/switch cases to support more media types in the future. - if mediaType == pbs.MEDIA_TYPE_VIDEO { - result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeVideo) - } else { - result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeBanner) - } - } - } - ch <- result - }(bidder, obj.requestJson, obj.mediaType) - } - - bids := make(pbs.PBSBidSlice, 0) - for i := 0; i < len(callOneObjects); i++ { - result := <-ch - if result.Bid != nil && result.Bid.Price != 0 { - bids = append(bids, result.Bid) - } - if req.IsDebug { - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - RequestBody: callOneObjects[i].requestJson.String(), - StatusCode: result.StatusCode, - ResponseBody: result.ResponseBody, - } - bidder.Debug = append(bidder.Debug, debug) - } - if result.Error != nil { - if glog.V(2) { - glog.Infof("Error from rubicon adapter: %v", result.Error) - } - err = result.Error - } - } - - if len(bids) == 0 { - return nil, err - } - return bids, nil -} - func resolveVideoSizeId(placement openrtb2.VideoPlacementType, instl int8, impId string) (sizeID int, err error) { if placement != 0 { if placement == 1 { @@ -665,19 +349,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func NewRubiconLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, uri string, xuser string, xpass string, tracker string) *RubiconAdapter { - a := adapters.NewHTTPAdapter(httpConfig) - - uri = appendTrackerToUrl(uri, tracker) - - return &RubiconAdapter{ - http: a, - URI: uri, - XAPIUsername: xuser, - XAPIPassword: xpass, - } -} - func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) errs := make([]error, 0, len(request.Imp)) diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 9bfa04fa78f..0d88937b6da 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1,27 +1,17 @@ package rubicon import ( - "bytes" - "context" "encoding/json" "errors" - "fmt" - "io/ioutil" "net/http" - "net/http/httptest" "strconv" - "strings" "testing" - "time" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -48,291 +38,17 @@ type rubiSetNetworkIdTestScenario struct { isNetworkIdSet bool } -type rubiTagInfo struct { - code string - zoneID int - bid float64 - content string - adServerTargeting map[string]string - mediaType string -} - type rubiBidInfo struct { - domain string - page string - accountID int - siteID int - tags []rubiTagInfo - deviceIP string - deviceUA string - buyerUID string - xapiuser string - xapipass string - delay time.Duration - visitorTargeting string - inventoryTargeting string - sdkVersion string - sdkPlatform string - sdkSource string - devicePxRatio float64 + domain string + page string + deviceIP string + deviceUA string + buyerUID string + devicePxRatio float64 } var rubidata rubiBidInfo -func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { - defer func() { - err := r.Body.Close() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }() - - 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 len(breq.Imp) > 1 { - http.Error(w, "Rubicon adapter only supports one Imp per request", http.StatusInternalServerError) - return - } - imp := breq.Imp[0] - var rix rubiconImpExt - err = json.Unmarshal(imp.Ext, &rix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - impTargetingString, _ := json.Marshal(&rix.RP.Target) - if string(impTargetingString) != rubidata.inventoryTargeting { - http.Error(w, fmt.Sprintf("Inventory FPD targeting '%s' doesn't match '%s'", string(impTargetingString), rubidata.inventoryTargeting), http.StatusInternalServerError) - return - } - if rix.RP.Track.Mint != "prebid" { - http.Error(w, fmt.Sprintf("Track mint '%s' doesn't match '%s'", rix.RP.Track.Mint, "prebid"), http.StatusInternalServerError) - return - } - mintVersionString := rubidata.sdkSource + "_" + rubidata.sdkPlatform + "_" + rubidata.sdkVersion - if rix.RP.Track.MintVersion != mintVersionString { - http.Error(w, fmt.Sprintf("Track mint version '%s' doesn't match '%s'", rix.RP.Track.MintVersion, mintVersionString), http.StatusInternalServerError) - return - } - - ix := -1 - - for i, tag := range rubidata.tags { - if rix.RP.ZoneID == tag.zoneID { - ix = i - } - } - if ix == -1 { - http.Error(w, fmt.Sprintf("Zone %d not found", rix.RP.ZoneID), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 2), - }, - }, - } - - if imp.Banner != nil { - var bix rubiconBannerExt - err = json.Unmarshal(imp.Banner.Ext, &bix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if bix.RP.SizeID != 15 { // 300x250 - http.Error(w, fmt.Sprintf("Primary size ID isn't 15"), http.StatusInternalServerError) - return - } - if len(bix.RP.AltSizeIDs) != 1 || bix.RP.AltSizeIDs[0] != 10 { // 300x600 - http.Error(w, fmt.Sprintf("Alt size ID isn't 10"), http.StatusInternalServerError) - return - } - if bix.RP.MIME != "text/html" { - http.Error(w, fmt.Sprintf("MIME isn't text/html"), http.StatusInternalServerError) - return - } - } - - if imp.Video != nil { - var vix rubiconVideoExt - err = json.Unmarshal(imp.Video.Ext, &vix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if len(imp.Video.MIMEs) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.mimes array"), http.StatusInternalServerError) - return - } - if len(imp.Video.Protocols) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.protocols array"), http.StatusInternalServerError) - return - } - for _, protocol := range imp.Video.Protocols { - if protocol < 1 || protocol > 8 { - http.Error(w, fmt.Sprintf("Invalid video protocol %d", protocol), http.StatusInternalServerError) - return - } - } - } - - targeting := "{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}" - rawTargeting := json.RawMessage(targeting) - - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "random-id", - ImpID: imp.ID, - Price: rubidata.tags[ix].bid, - AdM: rubidata.tags[ix].content, - Ext: rawTargeting, - } - - if breq.Site == nil { - http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) - return - } - if breq.Site.Domain != rubidata.domain { - http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, rubidata.domain), http.StatusInternalServerError) - return - } - if breq.Site.Page != rubidata.page { - http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, rubidata.page), http.StatusInternalServerError) - return - } - var rsx rubiconSiteExt - err = json.Unmarshal(breq.Site.Ext, &rsx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if rsx.RP.SiteID != rubidata.siteID { - http.Error(w, fmt.Sprintf("SiteID '%d' doesn't match '%d", rsx.RP.SiteID, rubidata.siteID), http.StatusInternalServerError) - return - } - if breq.Site.Publisher == nil { - http.Error(w, fmt.Sprintf("No site.publisher object sent"), http.StatusInternalServerError) - return - } - var rpx rubiconPubExt - err = json.Unmarshal(breq.Site.Publisher.Ext, &rpx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if rpx.RP.AccountID != rubidata.accountID { - http.Error(w, fmt.Sprintf("AccountID '%d' doesn't match '%d'", rpx.RP.AccountID, rubidata.accountID), http.StatusInternalServerError) - return - } - if breq.Device.UA != rubidata.deviceUA { - http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s'", breq.Device.UA, rubidata.deviceUA), http.StatusInternalServerError) - return - } - if breq.Device.IP != rubidata.deviceIP { - http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s'", breq.Device.IP, rubidata.deviceIP), http.StatusInternalServerError) - return - } - if breq.Device.PxRatio != rubidata.devicePxRatio { - http.Error(w, fmt.Sprintf("Pixel ratio '%f' doesn't match '%f'", breq.Device.PxRatio, rubidata.devicePxRatio), http.StatusInternalServerError) - return - } - if breq.User.BuyerUID != rubidata.buyerUID { - http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s'", breq.User.BuyerUID, rubidata.buyerUID), http.StatusInternalServerError) - return - } - - var rux rubiconUserExt - err = json.Unmarshal(breq.User.Ext, &rux) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - userTargetingString, _ := json.Marshal(&rux.RP.Target) - if string(userTargetingString) != rubidata.visitorTargeting { - http.Error(w, fmt.Sprintf("User FPD targeting '%s' doesn't match '%s'", string(userTargetingString), rubidata.visitorTargeting), http.StatusInternalServerError) - return - } - - if rubidata.delay > 0 { - <-time.After(rubidata.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 TestRubiconBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.Nil(t, err, "Should not have gotten an error: %v", err) - assert.Equal(t, 2, len(bids), "Received %d bids instead of 3", len(bids)) - - for _, bid := range bids { - matched := false - for _, tag := range rubidata.tags { - if bid.AdUnitCode == tag.code { - matched = true - - assert.Equal(t, "rubicon", bid.BidderCode, "Incorrect BidderCode '%s'", bid.BidderCode) - - assert.Equal(t, tag.bid, bid.Price, "Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) - - assert.Equal(t, tag.content, bid.Adm, "Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - - assert.Equal(t, bid.AdServerTargeting, tag.adServerTargeting, - "Incorrect targeting '%+v' expected '%+v'", bid.AdServerTargeting, tag.adServerTargeting) - - assert.Equal(t, tag.mediaType, bid.CreativeMediaType, "Incorrect media type '%s' expected '%s'", bid.CreativeMediaType, tag.mediaType) - } - } - assert.True(t, matched, "Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - - // same test but with request timing out - rubidata.delay = 20 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - bids, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten a timeout error: %v", err) -} - -func TestRubiconUserSyncInfo(t *testing.T) { - conf := *adapters.DefaultHTTPAdapterConfig - an := NewRubiconLegacyAdapter(&conf, "uri", "xuser", "xpass", "pbs-test-tracker") - - assert.Equal(t, "rubicon", an.Name(), "Name '%s' != 'rubicon'", an.Name()) - - assert.False(t, an.SkipNoCookies(), "SkipNoCookies should be false") -} - func getTestSizes() map[int]openrtb2.Format { return map[int]openrtb2.Format{ 15: {W: 300, H: 250}, @@ -703,368 +419,6 @@ func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 { 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) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Equal(t, 204, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 204 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestNotFoundResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Equal(t, 404, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 404 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "HTTP status 404"), - "Should start with 'HTTP status' instead of: %v", err.Error()) -} - -func TestWrongFormatResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("This is text.")) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Equal(t, 200, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 200 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "invalid character"), - "Should start with 'invalid character' instead of: %v", err) -} - -func TestZeroSeatBidResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{}, - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestEmptyBidResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 0), - }, - }, - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestWrongBidIdResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 2), - }, - }, - } - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "random-id", - ImpID: "zma", - Price: 1.67, - AdM: "zma", - Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.NotNil(t, err, "Should not have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "Unknown ad unit code"), - "Should start with 'Unknown ad unit code' instead of: %v", err) -} - -func TestZeroPriceBidResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 1), - }, - }, - } - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "test-bid-id", - ImpID: "first-tag", - Price: 0, - AdM: "zma", - Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Nil(t, b, "\n\n\n0 price bids are being included %d, err : %v", len(b), err) -} - -func TestDifferentRequest(t *testing.T) { - SIZE_ID := getTestSizes() - server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - - // test app not nil - pbReq.App = &openrtb2.App{ - ID: "com.test", - Name: "testApp", - } - - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set app back to normal - pbReq.App = nil - - // test video media type - pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO} - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set media back to normal - pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - - // test wrong params - pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %s, \"siteId\": %d, \"visitor\": %s, \"inventory\": %s}", "zma", rubidata.siteID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set params back to normal - pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", 8394, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) - - // test invalid size - pbReq.Bidders[0].AdUnits[0].Sizes = []openrtb2.Format{ - { - W: 2222, - H: 333, - }, - } - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ - { - W: 222, - H: 3333, - }, - { - W: 350, - H: 270, - }, - } - pbReq.Bidders[0].AdUnits = pbReq.Bidders[0].AdUnits[:len(pbReq.Bidders[0].AdUnits)-1] - b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ - { - W: 222, - H: 3333, - }, - SIZE_ID[10], - SIZE_ID[15], - } - b, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.Nil(t, err, "Should have not gotten an error: %v", err) - - assert.Equal(t, 1, len(b), - "Filtering bids based on ad unit sizes failed. Got %d bids instead of 1, error = %v", len(b), err) -} - -func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdapter, ctx context.Context, pbReq *pbs.PBSRequest) { - SIZE_ID := getTestSizes() - rubidata = rubiBidInfo{ - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - accountID: 7891, - siteID: 283282, - tags: make([]rubiTagInfo, 3), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "need-an-actual-rp-id", - visitorTargeting: "[\"v1\",\"v2\"]", - inventoryTargeting: "[\"i1\",\"i2\"]", - sdkVersion: "2.0.0", - sdkPlatform: "iOS", - sdkSource: "some-sdk", - devicePxRatio: 4.0, - } - - targeting := make(map[string]string, 2) - targeting["key1"] = "value1" - targeting["key2"] = "value2" - - rubidata.tags[0] = rubiTagInfo{ - code: "first-tag", - zoneID: 8394, - bid: 1.67, - adServerTargeting: targeting, - mediaType: "banner", - } - rubidata.tags[1] = rubiTagInfo{ - code: "second-tag", - zoneID: 8395, - bid: 3.22, - adServerTargeting: targeting, - mediaType: "banner", - } - rubidata.tags[2] = rubiTagInfo{ - code: "video-tag", - zoneID: 7780, - bid: 23.12, - adServerTargeting: targeting, - mediaType: "video", - } - - conf := *adapters.DefaultHTTPAdapterConfig - an = NewRubiconLegacyAdapter(&conf, "uri", rubidata.xapiuser, rubidata.xapipass, "pbs-test-tracker") - an.URI = server.URL - - pbin := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 3), - Device: &openrtb2.Device{PxRatio: rubidata.devicePxRatio}, - SDK: &pbs.SDK{Source: rubidata.sdkSource, Platform: rubidata.sdkPlatform, Version: rubidata.sdkVersion}, - } - - for i, tag := range rubidata.tags { - pbin.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb2.Format{ - SIZE_ID[10], - SIZE_ID[15], - }, - Bids: []pbs.Bids{ - { - BidderCode: "rubicon", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", tag.zoneID, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)), - }, - }, - } - if tag.mediaType == "video" { - pbin.AdUnits[i].Video = pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{1, 2, 3, 4, 5}, - } - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbin) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - - req := httptest.NewRequest("POST", server.URL, body) - req.Header.Add("Referer", rubidata.page) - req.Header.Add("User-Agent", rubidata.deviceUA) - req.Header.Add("X-Real-IP", rubidata.deviceIP) - - pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) - pc.TrySync("rubicon", rubidata.buyerUID) - 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) - pbReq.IsDebug = true - - assert.Nil(t, err, "ParsePBSRequest failed: %v", err) - - assert.Equal(t, 1, len(pbReq.Bidders), - "ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - - assert.Equal(t, "rubicon", pbReq.Bidders[0].BidderCode, - "ParsePBSRequest returned invalid bidder") - - ctx = context.TODO() - return -} - func TestOpenRTBRequest(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) diff --git a/adapters/sharethrough/utils_test.go b/adapters/sharethrough/utils_test.go index b842cf0b0c0..70e97947880 100644 --- a/adapters/sharethrough/utils_test.go +++ b/adapters/sharethrough/utils_test.go @@ -151,10 +151,6 @@ type userAgentTest struct { expected bool } -type userAgentFailureTest struct { - input string -} - func runUserAgentTests(tests map[string]userAgentTest, fn func(string) bool, t *testing.T) { for testName, test := range tests { t.Logf("Test case: %s\n", testName) diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go index 690d5f59f67..0ff71cdb0e5 100644 --- a/adapters/sonobi/sonobi.go +++ b/adapters/sonobi/sonobi.go @@ -25,10 +25,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -type sonobiParams struct { - TagID string `json:"TagID"` -} - // MakeRequests Makes the OpenRTB request payload func (a *SonobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error @@ -152,9 +148,3 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), } } - -func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { - if len(headerValue) > 0 { - headers.Add(headerName, headerValue) - } -} diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 40969d3638e..98264ce3a1b 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -1,176 +1,23 @@ package sovrn import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "net/url" - "sort" "strconv" "strings" - "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" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type SovrnAdapter struct { - http *adapters.HTTPAdapter - URI string -} - -// Name - export adapter name */ -func (s *SovrnAdapter) Name() string { - return "sovrn" -} - -// FamilyName used for cookies and such -func (s *SovrnAdapter) FamilyName() string { - return "sovrn" -} - -func (s *SovrnAdapter) SkipNoCookies() bool { - return false -} - -// Call send bid requests to sovrn and receive responses -func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - sReq, err := adapters.MakeOpenRTBGeneric(req, bidder, s.FamilyName(), supportedMediaTypes) - - if err != nil { - return nil, err - } - - sovrnReq := openrtb2.BidRequest{ - ID: sReq.ID, - Imp: sReq.Imp, - Site: sReq.Site, - User: sReq.User, - Regs: sReq.Regs, - } - - // add tag ids to impressions - for i, unit := range bidder.AdUnits { - var params openrtb_ext.ExtImpSovrn - err = json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, err - } - - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(sovrnReq.Imp) <= i { - break - } - sovrnReq.Imp[i].TagID = getTagid(params) - } - - reqJSON, err := json.Marshal(sovrnReq) - if err != nil { - return nil, err - } - - debug := &pbs.BidderDebug{ - RequestURI: s.URI, - } - - httpReq, _ := http.NewRequest("POST", s.URI, bytes.NewReader(reqJSON)) - httpReq.Header.Set("Content-Type", "application/json") - if sReq.Device != nil { - addHeaderIfNonEmpty(httpReq.Header, "User-Agent", sReq.Device.UA) - addHeaderIfNonEmpty(httpReq.Header, "X-Forwarded-For", sReq.Device.IP) - addHeaderIfNonEmpty(httpReq.Header, "Accept-Language", sReq.Device.Language) - if sReq.Device.DNT != nil { - addHeaderIfNonEmpty(httpReq.Header, "DNT", strconv.Itoa(int(*sReq.Device.DNT))) - } - } - if sReq.User != nil { - userID := strings.TrimSpace(sReq.User.BuyerUID) - if len(userID) > 0 { - httpReq.AddCookie(&http.Cookie{Name: "ljt_reader", Value: userID}) - } - } - sResp, err := ctxhttp.Do(ctx, s.http.Client, httpReq) - if err != nil { - return nil, err - } - defer sResp.Body.Close() - - debug.StatusCode = sResp.StatusCode - - if sResp.StatusCode == http.StatusNoContent { - return nil, nil - } - - body, err := ioutil.ReadAll(sResp.Body) - if err != nil { - return nil, err - } - responseBody := string(body) - - if sResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", sResp.StatusCode, responseBody), - } - } - - if sResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", sResp.StatusCode, responseBody), - } - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - debug.ResponseBody = responseBody - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - adm, _ := url.QueryUnescape(bid.AdM) - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: adm, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - NURL: bid.NURL, - } - bids = append(bids, &pbid) - } - } - - sort.Sort(bids) - return bids, nil + URI string } func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -303,14 +150,6 @@ func getTagid(sovrnExt openrtb_ext.ExtImpSovrn) string { } } -// NewSovrnLegacyAdapter create a new SovrnAdapter instance -func NewSovrnLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *SovrnAdapter { - return &SovrnAdapter{ - http: adapters.NewHTTPAdapter(config), - URI: endpoint, - } -} - // Builder builds a new instance of the Sovrn adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &SovrnAdapter{ diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index 407c505437a..49ed52844f3 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -1,28 +1,11 @@ package sovrn import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http/httptest" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "context" - "net/http" - - "strconv" - "time" - - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestJsonSamples(t *testing.T) { @@ -35,262 +18,3 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "sovrntest", bidder) } - -// ---------------------------------------------------------------------------- -// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb2. - -var testSovrnUserId = "SovrnUser123" -var testUserAgent = "user-agent-test" -var testUrl = "http://news.pub/topnews" -var testIp = "123.123.123.123" - -func TestSovrnAdapterNames(t *testing.T) { - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://sovrn/rtb/bid") - adapterstest.VerifyStringValue(adapter.Name(), "sovrn", t) - adapterstest.VerifyStringValue(adapter.FamilyName(), "sovrn", t) -} - -func TestSovrnAdapter_SkipNoCookies(t *testing.T) { - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://sovrn/rtb/bid") - adapterstest.VerifyBoolValue(adapter.SkipNoCookies(), false, t) -} - -func TestSovrnOpenRtbRequest(t *testing.T) { - service := CreateSovrnService(adapterstest.BidOnTags("")) - server := service.Server - ctx := context.Background() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - adapter.Call(ctx, req, bidder) - - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) - adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "123456", t) - adapterstest.VerifyBannerSize(service.LastBidRequest.Imp[0].Banner, 728, 90, t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -func TestSovrnBiddingBehavior(t *testing.T) { - service := CreateSovrnService(adapterstest.BidOnTags("123456")) - server := service.Server - ctx := context.TODO() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[0].BidderCode, "sovrn", t) - adapterstest.VerifyStringValue(bids[0].Adm, "
This is an Ad
", t) - adapterstest.VerifyStringValue(bids[0].Creative_id, "Cr-234", t) - adapterstest.VerifyIntValue(int(bids[0].Width), 728, t) - adapterstest.VerifyIntValue(int(bids[0].Height), 90, t) - adapterstest.VerifyIntValue(int(bids[0].Price*100), 210, t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -/** - * Verify bidding behavior on multiple impressions, some impressions make a bid - */ -func TestSovrntMultiImpPartialBidding(t *testing.T) { - // setup server endpoint to return bid. - service := CreateSovrnService(adapterstest.BidOnTags("123456")) - server := service.Server - ctx := context.TODO() - req := SampleSovrnRequest(2, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -/** - * Verify bidding behavior on multiple impressions, all impressions passed back. - */ -func TestSovrnMultiImpAllBid(t *testing.T) { - // setup server endpoint to return bid. - service := CreateSovrnService(adapterstest.BidOnTags("123456,123457")) - server := service.Server - ctx := context.TODO() - req := SampleSovrnRequest(2, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 2, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[1].AdUnitCode, "div-adunit-2", t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -func checkHttpRequest(req http.Request, t *testing.T) { - adapterstest.VerifyStringValue(req.Header.Get("Accept-Language"), "murican", t) - var cookie, _ = req.Cookie("ljt_reader") - adapterstest.VerifyStringValue((*cookie).Value, testSovrnUserId, t) - adapterstest.VerifyStringValue(req.Header.Get("User-Agent"), testUserAgent, t) - adapterstest.VerifyStringValue(req.Header.Get("Content-Type"), "application/json", t) - adapterstest.VerifyStringValue(req.Header.Get("X-Forwarded-For"), testIp, t) - adapterstest.VerifyStringValue(req.Header.Get("DNT"), "0", t) -} - -func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { - dnt := int8(0) - device := openrtb2.Device{ - Language: "murican", - DNT: &dnt, - } - - user := openrtb2.User{ - ID: testSovrnUserId, - } - - req := pbs.PBSRequest{ - AccountID: "1", - AdUnits: make([]pbs.AdUnit, 2), - Device: &device, - User: &user, - } - - tagID := 123456 - - for i := 0; i < numberOfImpressions; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb2.Format{ - { - W: 728, - H: 90, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "sovrn", - BidID: fmt.Sprintf("Bid-%d", i+1), - Params: json.RawMessage(fmt.Sprintf("{\"tagid\": \"%s\" }", strconv.Itoa(tagID+i))), - }, - }, - } - - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(req) - if err != nil { - t.Fatalf("Error when serializing request") - } - - httpReq := httptest.NewRequest("POST", CreateSovrnService(adapterstest.BidOnTags("")).Server.URL, body) - httpReq.Header.Add("Referer", testUrl) - httpReq.Header.Add("User-Agent", testUserAgent) - httpReq.Header.Add("X-Forwarded-For", testIp) - pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) - pc.TrySync("sovrn", testSovrnUserId) - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - // parse the http request - cacheClient, _ := dummycache.New() - hcc := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcc) - if err != nil { - t.Fatalf("Error when parsing request: %v", err) - } - return parsedReq - -} - -func TestNoContentResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) - })) - defer server.Close() - - ctx := context.TODO() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - _, err := adapter.Call(ctx, req, bidder) - - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - -} - -func TestNotFoundResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - defer server.Close() - - ctx := context.TODO() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - _, err := adapter.Call(ctx, req, bidder) - - adapterstest.VerifyStringValue(err.Error(), "HTTP status 404; body: ", t) - -} - -func CreateSovrnService(tagsToBid map[string]bool) adapterstest.OrtbMockService { - service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb2.BidRequest - var lastHttpReq http.Request - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - lastHttpReq = *r - 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 - } - lastBidRequest = breq - var bids []openrtb2.Bid - for i, imp := range breq.Imp { - if tagsToBid[imp.TagID] { - bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) - } - } - - // serialize the bids to openrtb2.BidResponse - js, _ := json.Marshal(openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: bids, - }, - }, - }) - w.Header().Set("Content-Type", "application/json") - w.Write(js) - })) - - service.Server = server - service.LastBidRequest = &lastBidRequest - service.LastHttpRequest = &lastHttpReq - - return service -} diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index a0721d98a2a..43853382354 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -102,8 +102,6 @@ func NewFileLogger(filename string) (analytics.PBSAnalyticsModule, error) { } } -type fileAuctionObject analytics.AuctionObject - func jsonifyAuctionObject(ao *analytics.AuctionObject) string { type alias analytics.AuctionObject b, err := json.Marshal(&struct { diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index cb8f088d0bf..0e0b3634508 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -2,59 +2,15 @@ package pubstack import ( "encoding/json" - "io/ioutil" "net/http" "net/http/httptest" - "os" "testing" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/stretchr/testify/assert" ) -func loadJSONFromFile() (*analytics.AuctionObject, error) { - req, err := os.Open("mocks/mock_openrtb_request.json") - if err != nil { - return nil, err - } - defer req.Close() - - reqCtn := openrtb2.BidRequest{} - reqPayload, err := ioutil.ReadAll(req) - if err != nil { - return nil, err - } - - err = json.Unmarshal(reqPayload, &reqCtn) - if err != nil { - return nil, err - } - - res, err := os.Open("mocks/mock_openrtb_response.json") - if err != nil { - return nil, err - } - defer res.Close() - - resCtn := openrtb2.BidResponse{} - resPayload, err := ioutil.ReadAll(res) - if err != nil { - return nil, err - } - - err = json.Unmarshal(resPayload, &resCtn) - if err != nil { - return nil, err - } - - return &analytics.AuctionObject{ - Request: &reqCtn, - Response: &resCtn, - }, nil -} - func TestPubstackModuleErrors(t *testing.T) { tests := []struct { description string diff --git a/cache/dummycache/dummycache.go b/cache/dummycache/dummycache.go deleted file mode 100644 index 02fe726d043..00000000000 --- a/cache/dummycache/dummycache.go +++ /dev/null @@ -1,65 +0,0 @@ -package dummycache - -import ( - "fmt" - - "github.com/prebid/prebid-server/cache" -) - -// Cache dummy config that will echo back results -type Cache struct { - accounts *accountService - config *configService -} - -// New creates new dummy.Cache -func New() (*Cache, error) { - return &Cache{ - accounts: &accountService{}, - config: &configService{}, - }, nil -} - -func (c *Cache) Accounts() cache.AccountsService { - return c.accounts -} -func (c *Cache) Config() cache.ConfigService { - return c.config -} - -// AccountService handles the account information -type accountService struct { -} - -// Get echos back the account -func (s *accountService) Get(id string) (*cache.Account, error) { - return &cache.Account{ - ID: id, - }, nil -} - -// ConfigService not supported, always returns an error -type configService struct { - c string -} - -// Get not supported, always returns an error -func (s *configService) Get(id string) (string, error) { - if s.c == "" { - return s.c, fmt.Errorf("No configuration provided") - } - return s.c, nil -} - -// Set will set a string in memory as the configuration -// this is so we can use it in tests such as pbs/pbsrequest_test.go -// it will ignore the id so this will pass tests -func (s *configService) Set(id, val string) error { - s.c = val - return nil -} - -// Close will always return nil -func (c *Cache) Close() error { - return nil -} diff --git a/cache/dummycache/dummycache_test.go b/cache/dummycache/dummycache_test.go deleted file mode 100644 index 74004feaa38..00000000000 --- a/cache/dummycache/dummycache_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package dummycache - -import "testing" - -func TestDummyCache(t *testing.T) { - - c, _ := New() - - account, err := c.Accounts().Get("account1") - if err != nil { - t.Fatal(err) - } - - if account.ID != "account1" { - t.Error("Wrong account returned") - } - - if err := c.Config().Set("config", "abc123"); err != nil { - t.Errorf("Dummy config should return nil") - } - - cfg, err := c.Config().Get("config") - if err != nil { - t.Error("Dummy configs should be supported") - } - - if cfg != "abc123" { - t.Error("Dummy config did not return back expected string") - } - -} diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go deleted file mode 100644 index 7bc4bea43f0..00000000000 --- a/cache/filecache/filecache.go +++ /dev/null @@ -1,123 +0,0 @@ -package filecache - -import ( - "fmt" - "io/ioutil" - - "github.com/golang/glog" - "github.com/prebid/prebid-server/cache" - "gopkg.in/yaml.v2" -) - -type shared struct { - Configs map[string]string - Accounts map[string]bool -} - -// Cache is a file backed cache -type Cache struct { - shared *shared - accounts *accountService - config *configService -} - -type fileConfig struct { - ID string `yaml:"id"` - Config string `yaml:"config"` -} - -type fileCacheFile struct { - Configs []fileConfig `yaml:"configs"` - Accounts []string `yaml:"accounts"` -} - -// New will load the file into memory -func New(filename string) (*Cache, error) { - if glog.V(2) { - glog.Infof("Reading inventory urls from %s", filename) - } - - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - if glog.V(2) { - glog.Infof("Parsing filecache YAML") - } - - var u fileCacheFile - if err = yaml.Unmarshal(b, &u); err != nil { - return nil, err - } - - if glog.V(2) { - glog.Infof("Building URL map") - } - - s := &shared{} - - s.Configs = make(map[string]string, len(u.Configs)) - for _, config := range u.Configs { - s.Configs[config.ID] = config.Config - } - glog.Infof("Loaded %d configs", len(u.Configs)) - - s.Accounts = make(map[string]bool, len(u.Accounts)) - for _, Account := range u.Accounts { - s.Accounts[Account] = true - } - glog.Infof("Loaded %d accounts", len(u.Accounts)) - - return &Cache{ - shared: s, - accounts: &accountService{s}, - config: &configService{s}, - }, nil -} - -// This empty function exists so the Cache struct implements the Cache interface defined in cache/legacy.go -func (c *Cache) Close() error { - return nil -} - -func (c *Cache) Accounts() cache.AccountsService { - return c.accounts -} -func (c *Cache) Config() cache.ConfigService { - return c.config -} - -// AccountService handles the account information -type accountService struct { - shared *shared -} - -// Get will return Account from memory if it exists -func (s *accountService) Get(id string) (*cache.Account, error) { - if _, ok := s.shared.Accounts[id]; !ok { - return nil, fmt.Errorf("Not found") - } - return &cache.Account{ - ID: id, - }, nil -} - -// ConfigService not supported, always returns an error -type configService struct { - shared *shared -} - -// Get will return config from memory if it exists -func (s *configService) Get(id string) (string, error) { - cfg, ok := s.shared.Configs[id] - if !ok { - return "", fmt.Errorf("Not found") - } - return cfg, nil -} - -// Set not supported, always returns an error -func (s *configService) Set(id, value string) error { - return fmt.Errorf("Not supported") -} diff --git a/cache/filecache/filecache_test.go b/cache/filecache/filecache_test.go deleted file mode 100644 index 80a72803bbe..00000000000 --- a/cache/filecache/filecache_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package filecache - -import ( - "io/ioutil" - "os" - "testing" - - yaml "gopkg.in/yaml.v2" -) - -func TestFileCache(t *testing.T) { - fcf := fileCacheFile{ - Accounts: []string{"account1", "account2", "account3"}, - Configs: []fileConfig{ - { - ID: "one", - Config: "config1", - }, { - ID: "two", - Config: "config2", - }, { - ID: "three", - Config: "config3", - }, - }, - } - - bytes, err := yaml.Marshal(&fcf) - if err != nil { - t.Fatal(err) - } - - tmpfile, err := ioutil.TempFile("", "filecache") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpfile.Name()) - - if _, err := tmpfile.Write(bytes); err != nil { - t.Fatal(err) - } - - if err := tmpfile.Close(); err != nil { - t.Fatal(err) - } - - dataCache, err := New(tmpfile.Name()) - if err != nil { - t.Fatal(err) - } - - a, err := dataCache.Accounts().Get("account1") - if err != nil { - t.Fatal(err) - } - - if a.ID != "account1" { - t.Error("fetched invalid account") - } - - a, err = dataCache.Accounts().Get("abc123") - if err == nil { - t.Error("account should not exist in cache") - } - - c, err := dataCache.Config().Get("one") - if err != nil { - t.Fatal(err) - } - - if c != "config1" { - t.Error("fetched invalid config") - } - - c, err = dataCache.Config().Get("abc123") - if err == nil { - t.Error("config should not exist in cache") - } -} diff --git a/cache/legacy.go b/cache/legacy.go deleted file mode 100644 index 19c5ae5a4fe..00000000000 --- a/cache/legacy.go +++ /dev/null @@ -1,33 +0,0 @@ -package cache - -type Domain struct { - Domain string `json:"domain"` -} - -type App struct { - Bundle string `json:"bundle"` -} - -type Account struct { - ID string `json:"id"` - PriceGranularity string `json:"price_granularity"` -} - -type Configuration struct { - Type string `json:"type"` // required -} - -type Cache interface { - Close() error - Accounts() AccountsService - Config() ConfigService -} - -type AccountsService interface { - Get(string) (*Account, error) -} - -type ConfigService interface { - Get(string) (string, error) - Set(string, string) error -} diff --git a/cache/postgrescache/postgrescache.go b/cache/postgrescache/postgrescache.go deleted file mode 100644 index 2333e08269e..00000000000 --- a/cache/postgrescache/postgrescache.go +++ /dev/null @@ -1,139 +0,0 @@ -package postgrescache - -import ( - "bytes" - "context" - "database/sql" - "encoding/gob" - "time" - - "github.com/prebid/prebid-server/stored_requests" - - "github.com/coocood/freecache" - "github.com/lib/pq" - "github.com/prebid/prebid-server/cache" -) - -type CacheConfig struct { - TTL int - Size int -} - -// shared configuration that get used by all of the services -type shared struct { - db *sql.DB - lru *freecache.Cache - ttlSeconds int -} - -// Cache postgres -type Cache struct { - shared *shared - accounts *accountService - config *configService -} - -// New creates new postgres.Cache -func New(db *sql.DB, cfg CacheConfig) *Cache { - shared := &shared{ - db: db, - lru: freecache.NewCache(cfg.Size), - ttlSeconds: cfg.TTL, - } - return &Cache{ - shared: shared, - accounts: &accountService{shared: shared}, - config: &configService{shared: shared}, - } -} - -func (c *Cache) Accounts() cache.AccountsService { - return c.accounts -} -func (c *Cache) Config() cache.ConfigService { - return c.config -} - -func (c *Cache) Close() error { - return c.shared.db.Close() -} - -// AccountService handles the account information -type accountService struct { - shared *shared -} - -// Get echos back the account -func (s *accountService) Get(key string) (*cache.Account, error) { - var account cache.Account - - b, err := s.shared.lru.Get([]byte(key)) - if err == nil { - return decodeAccount(b), nil - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50)*time.Millisecond) - defer cancel() - var id string - var priceGranularity sql.NullString - if err := s.shared.db.QueryRowContext(ctx, "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1", key).Scan(&id, &priceGranularity); err != nil { - /* TODO -- We should store failed attempts in the LRU as well to stop from hitting to DB */ - return nil, err - } - - account.ID = id - if priceGranularity.Valid { - account.PriceGranularity = priceGranularity.String - } - - buf := bytes.Buffer{} - if err := gob.NewEncoder(&buf).Encode(&account); err != nil { - panic(err) - } - - s.shared.lru.Set([]byte(key), buf.Bytes(), s.shared.ttlSeconds) - return &account, nil -} - -func decodeAccount(b []byte) *cache.Account { - var account cache.Account - buf := bytes.NewReader(b) - if err := gob.NewDecoder(buf).Decode(&account); err != nil { - panic(err) - } - return &account -} - -// ConfigService -type configService struct { - shared *shared -} - -func (s *configService) Set(id, value string) error { - return nil -} - -func (s *configService) Get(key string) (string, error) { - if b, err := s.shared.lru.Get([]byte(key)); err == nil { - return string(b), nil - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50)*time.Millisecond) - defer cancel() - var config string - if err := s.shared.db.QueryRowContext(ctx, "SELECT config FROM s2sconfig_config where uuid = $1 LIMIT 1", key).Scan(&config); err != nil { - // TODO -- We should store failed attempts in the LRU as well to stop from hitting to DB - - // If the user didn't give us a UUID, the query fails with this error. Wrap it so that we don't - // pollute the app logs with bad user input. - if pqErr, ok := err.(*pq.Error); ok && string(pqErr.Code) == "22P02" { - err = &stored_requests.NotFoundError{ - ID: key, - DataType: "Legacy Config", - } - } - return "", err - } - s.shared.lru.Set([]byte(key), []byte(config), s.shared.ttlSeconds) - return config, nil -} diff --git a/cache/postgrescache/postgrescache_test.go b/cache/postgrescache/postgrescache_test.go deleted file mode 100644 index bab96f1a11e..00000000000 --- a/cache/postgrescache/postgrescache_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package postgrescache - -import ( - "database/sql" - "testing" - - "github.com/coocood/freecache" - "github.com/erikstmartin/go-testdb" -) - -type StubCache struct { - shared *shared - accounts *accountService - config *configService -} - -// New creates new postgres.Cache -func StubNew(cfg CacheConfig) *Cache { - shared := stubnewShared(cfg) - return &Cache{ - shared: shared, - accounts: &accountService{shared: shared}, - config: &configService{shared: shared}, - } -} - -func stubnewShared(conf CacheConfig) *shared { - db, _ := sql.Open("testdb", "") - - s := &shared{ - db: db, - lru: freecache.NewCache(conf.Size), - ttlSeconds: 0, - } - return s -} - -func TestPostgresDbPriceGranularity(t *testing.T) { - defer testdb.Reset() - - sql := "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1" - columns := []string{"uuid", "price_granularity"} - result := ` - bdc928ef-f725-4688-8171-c104cc715bdf,med - ` - testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result)) - - conf := CacheConfig{ - TTL: 3434, - Size: 100, - } - dataCache := StubNew(conf) - - account, err := dataCache.Accounts().Get("bdc928ef-f725-4688-8171-c104cc715bdf") - if err != nil { - t.Fatalf("test postgres db errored: %v", err) - } - - if account.ID != "bdc928ef-f725-4688-8171-c104cc715bdf" { - t.Error("Expected bdc928ef-f725-4688-8171-c104cc715bdf") - } - if account.PriceGranularity != "med" { - t.Error("Expected med") - } -} - -func TestPostgresDbNullPriceGranularity(t *testing.T) { - defer testdb.Reset() - - sql := "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1" - columns := []string{"uuid", "price_granularity"} - result := ` - bdc928ef-f725-4688-8171-c104cc715bdf - ` - testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result)) - - conf := CacheConfig{ - TTL: 3434, - Size: 100, - } - dataCache := StubNew(conf) - - account, err := dataCache.Accounts().Get("bdc928ef-f725-4688-8171-c104cc715bdf") - if err != nil { - t.Fatalf("test postgres db errored: %v", err) - } - - if account.ID != "bdc928ef-f725-4688-8171-c104cc715bdf" { - t.Error("Expected bdc928ef-f725-4688-8171-c104cc715bdf") - } - if account.PriceGranularity != "" { - t.Error("Expected null string") - } -} diff --git a/config/config.go b/config/config.go index 597ac1b41a7..179a30af1cc 100644 --- a/config/config.go +++ b/config/config.go @@ -33,7 +33,6 @@ type Configuration struct { RecaptchaSecret string `mapstructure:"recaptcha_secret"` HostCookie HostCookie `mapstructure:"host_cookie"` Metrics Metrics `mapstructure:"metrics"` - DataCache DataCache `mapstructure:"datacache"` StoredRequests StoredRequests `mapstructure:"stored_requests"` StoredRequestsAMP StoredRequests `mapstructure:"stored_amp_req"` CategoryMapping StoredRequests `mapstructure:"category_mapping"` @@ -85,10 +84,6 @@ type Configuration struct { GenerateBidID bool `mapstructure:"generate_bid_id"` // GenerateRequestID overrides the bidrequest.id in an AMP Request or an App Stored Request with a generated UUID if set to true. The default is false. GenerateRequestID bool `mapstructure:"generate_request_id"` - - // EnableLegacyAuction specifies if the original /auction endpoint with a custom PBS data model is allowed - // by the host. - EnableLegacyAuction bool `mapstructure:"enable_legacy_auction"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -401,13 +396,6 @@ func (m *PrometheusMetrics) Timeout() time.Duration { return time.Duration(m.TimeoutMillisRaw) * time.Millisecond } -type DataCache struct { - Type string `mapstructure:"type"` - Filename string `mapstructure:"filename"` - CacheSize int `mapstructure:"cache_size"` - TTLSeconds int `mapstructure:"ttl_seconds"` -} - // ExternalCache configures the externally accessible cache url. type ExternalCache struct { Scheme string `mapstructure:"scheme"` @@ -660,10 +648,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("metrics.prometheus.namespace", "") v.SetDefault("metrics.prometheus.subsystem", "") v.SetDefault("metrics.prometheus.timeout_ms", 10000) - v.SetDefault("datacache.type", "dummy") - v.SetDefault("datacache.filename", "") - v.SetDefault("datacache.cache_size", 0) - v.SetDefault("datacache.ttl_seconds", 0) v.SetDefault("category_mapping.filesystem.enabled", true) v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping") v.SetDefault("category_mapping.http.endpoint", "") @@ -954,7 +938,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("auto_gen_source_tid", true) v.SetDefault("generate_bid_id", false) v.SetDefault("generate_request_id", false) - v.SetDefault("enable_legacy_auction", false) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") diff --git a/config/config_test.go b/config/config_test.go index 819eb21f819..4477d127f63 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -130,7 +130,6 @@ func TestDefaults(t *testing.T) { cmpInts(t, "max_request_size", int(cfg.MaxRequestSize), 1024*256) cmpInts(t, "host_cookie.ttl_days", int(cfg.HostCookie.TTL), 90) cmpInts(t, "host_cookie.max_cookie_size_bytes", cfg.HostCookie.MaxCookieSizeBytes, 0) - cmpStrings(t, "datacache.type", cfg.DataCache.Type, "dummy") cmpStrings(t, "adapters.pubmatic.endpoint", cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint, "https://hbopenbid.pubmatic.com/translator?source=prebid-server") cmpInts(t, "currency_converter.fetch_interval_seconds", cfg.CurrencyConverter.FetchIntervalSeconds, 1800) cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") @@ -144,7 +143,6 @@ func TestDefaults(t *testing.T) { 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) - cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, false) //Assert purpose VendorExceptionMap hash tables were built correctly expectedTCF2 := TCF2{ @@ -316,11 +314,6 @@ metrics: account_adapter_details: true adapter_connections_metrics: true adapter_gdpr_request_blocked: true -datacache: - type: postgres - filename: /usr/db/db.db - cache_size: 10000000 - ttl_seconds: 3600 adapters: appnexus: endpoint: http://ib.adnxs.com/some/endpoint @@ -354,7 +347,6 @@ request_validation: ipv4_private_networks: ["1.1.1.0/24"] ipv6_private_networks: ["1111::/16", "2222::/16"] generate_bid_id: true -enable_legacy_auction: true `) var adapterExtraInfoConfig = []byte(` @@ -545,10 +537,6 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "metrics.influxdb.username", cfg.Metrics.Influxdb.Username, "admin") cmpStrings(t, "metrics.influxdb.password", cfg.Metrics.Influxdb.Password, "admin1324") cmpInts(t, "metrics.influxdb.metric_send_interval", cfg.Metrics.Influxdb.MetricSendInterval, 30) - cmpStrings(t, "datacache.type", cfg.DataCache.Type, "postgres") - cmpStrings(t, "datacache.filename", cfg.DataCache.Filename, "/usr/db/db.db") - cmpInts(t, "datacache.cache_size", cfg.DataCache.CacheSize, 10000000) - cmpInts(t, "datacache.ttl_seconds", cfg.DataCache.TTLSeconds, 3600) cmpStrings(t, "", cfg.CacheURL.GetBaseURL(), "http://prebidcache.net") cmpStrings(t, "", cfg.GetCachedAssetURL("a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11"), "http://prebidcache.net/cache?uuid=a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11") cmpStrings(t, "adapters.appnexus.endpoint", cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, "http://ib.adnxs.com/some/endpoint") @@ -579,7 +567,6 @@ func TestFullConfig(t *testing.T) { 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, "") - cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, true) } func TestUnmarshalAdapterExtraInfo(t *testing.T) { diff --git a/config/stored_requests.go b/config/stored_requests.go index ee78179eb65..e752e9e4d9d 100644 --- a/config/stored_requests.go +++ b/config/stored_requests.go @@ -133,7 +133,6 @@ func resolvedStoredRequestsConfig(cfg *Configuration) { cfg.StoredVideo.dataType = VideoDataType cfg.CategoryMapping.dataType = CategoryDataType cfg.Accounts.dataType = AccountDataType - return } func (cfg *StoredRequests) validate(errs []error) []error { diff --git a/config/structlog.go b/config/structlog.go index a91e5ab857e..911f475717d 100644 --- a/config/structlog.go +++ b/config/structlog.go @@ -12,7 +12,7 @@ import ( type logMsg func(string, ...interface{}) var mapregex = regexp.MustCompile(`mapstructure:"([^"]+)"`) -var blacklistregexp = []*regexp.Regexp{ +var blocklistregexp = []*regexp.Regexp{ regexp.MustCompile("password"), } @@ -84,7 +84,7 @@ func fieldNameByTag(f reflect.StructField) string { } func allowedName(name string) bool { - for _, r := range blacklistregexp { + for _, r := range blocklistregexp { if r.MatchString(name) { return false } diff --git a/endpoints/auction.go b/endpoints/auction.go deleted file mode 100644 index 10d2ced6c37..00000000000 --- a/endpoints/auction.go +++ /dev/null @@ -1,513 +0,0 @@ -package endpoints - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "runtime/debug" - "sort" - "strconv" - "time" - - "github.com/golang/glog" - "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/cache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy" - gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/prebid/prebid-server/usersync" -) - -var allSyncTypes []usersync.SyncType = []usersync.SyncType{usersync.SyncTypeIFrame, usersync.SyncTypeRedirect} - -type bidResult struct { - bidder *pbs.PBSBidder - bidList pbs.PBSBidSlice -} - -const defaultPriceGranularity = "med" - -func min(x, y int) int { - if x < y { - return x - } - return y -} - -func writeAuctionError(w http.ResponseWriter, s string, err error) { - var resp pbs.PBSResponse - if err != nil { - resp.Status = fmt.Sprintf("%s: %v", s, err) - } else { - resp.Status = s - } - b, err := json.Marshal(&resp) - if err != nil { - glog.Errorf("Failed to marshal auction error JSON: %s", err) - } else { - w.Write(b) - } -} - -type auction struct { - cfg *config.Configuration - syncersByBidder map[string]usersync.Syncer - gdprPerms gdpr.Permissions - metricsEngine metrics.MetricsEngine - dataCache cache.Cache - exchanges map[string]adapters.Adapter -} - -func Auction(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPerms gdpr.Permissions, metricsEngine metrics.MetricsEngine, dataCache cache.Cache, exchanges map[string]adapters.Adapter) httprouter.Handle { - a := &auction{ - cfg: cfg, - syncersByBidder: syncersByBidder, - gdprPerms: gdprPerms, - metricsEngine: metricsEngine, - dataCache: dataCache, - exchanges: exchanges, - } - return a.auction -} - -func (a *auction) auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - w.Header().Add("Content-Type", "application/json") - var labels = getDefaultLabels(r) - req, err := pbs.ParsePBSRequest(r, &a.cfg.AuctionTimeouts, a.dataCache, &(a.cfg.HostCookie)) - - defer a.recordMetrics(req, labels) - - if err != nil { - if glog.V(2) { - glog.Infof("Failed to parse /auction request: %v", err) - } - writeAuctionError(w, "Error parsing request", err) - labels.RequestStatus = metrics.RequestStatusBadInput - return - } - status := "OK" - setLabelSource(&labels, req, &status) - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(req.TimeoutMillis)) - defer cancel() - account, err := a.dataCache.Accounts().Get(req.AccountID) - if err != nil { - if glog.V(2) { - glog.Infof("Invalid account id: %v", err) - } - writeAuctionError(w, "Unknown account id", fmt.Errorf("Unknown account")) - labels.RequestStatus = metrics.RequestStatusBadInput - return - } - labels.PubID = req.AccountID - resp := pbs.PBSResponse{ - Status: status, - TID: req.Tid, - BidderStatus: req.Bidders, - } - ch := make(chan bidResult) - sentBids := 0 - for _, bidder := range req.Bidders { - if ex, ok := a.exchanges[bidder.BidderCode]; ok { - // Make sure we have an independent label struct for each bidder. We don't want to run into issues with the goroutine below. - blabels := metrics.AdapterLabels{ - Source: labels.Source, - RType: labels.RType, - Adapter: openrtb_ext.BidderName(bidder.BidderCode), - PubID: labels.PubID, - CookieFlag: labels.CookieFlag, - AdapterBids: metrics.AdapterBidPresent, - } - if skip := a.processUserSync(req, bidder, blabels, ex, &ctx); skip == true { - continue - } - sentBids++ - bidderRunner := a.recoverSafely(func(bidder *pbs.PBSBidder, aLabels metrics.AdapterLabels) { - - start := time.Now() - bidList, err := ex.Call(ctx, req, bidder) - a.metricsEngine.RecordAdapterTime(aLabels, time.Since(start)) - bidder.ResponseTime = int(time.Since(start) / time.Millisecond) - processBidResult(bidList, bidder, &aLabels, a.metricsEngine, err) - - ch <- bidResult{ - bidder: bidder, - bidList: bidList, - // Bidder done, record bidder metrics - } - a.metricsEngine.RecordAdapterRequest(aLabels) - }) - - go bidderRunner(bidder, blabels) - - } else if bidder.BidderCode == "lifestreet" { - bidder.Error = "Bidder is no longer available" - } else { - bidder.Error = "Unsupported bidder" - } - } - for i := 0; i < sentBids; i++ { - result := <-ch - for _, bid := range result.bidList { - resp.Bids = append(resp.Bids, bid) - } - } - if err := cacheAccordingToMarkup(req, &resp, ctx, a, &labels); err != nil { - writeAuctionError(w, "Prebid cache failed", err) - labels.RequestStatus = metrics.RequestStatusErr - return - } - if req.SortBids == 1 { - sortBidsAddKeywordsMobile(resp.Bids, req, account.PriceGranularity) - } - if glog.V(2) { - glog.Infof("Request for %d ad units on url %s by account %s got %d bids", len(req.AdUnits), req.Url, req.AccountID, len(resp.Bids)) - } - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - enc.Encode(resp) -} - -func (a *auction) recoverSafely(inner func(*pbs.PBSBidder, metrics.AdapterLabels)) func(*pbs.PBSBidder, metrics.AdapterLabels) { - return func(bidder *pbs.PBSBidder, labels metrics.AdapterLabels) { - defer func() { - if r := recover(); r != nil { - if bidder == nil { - glog.Errorf("Legacy auction recovered panic: %v. Stack trace is: %v", r, string(debug.Stack())) - } else { - glog.Errorf("Legacy auction recovered panic from Bidder %s: %v. Stack trace is: %v", bidder.BidderCode, r, string(debug.Stack())) - } - a.metricsEngine.RecordAdapterPanic(labels) - } - }() - inner(bidder, labels) - } -} - -func (a *auction) shouldUsersync(ctx context.Context, bidder openrtb_ext.BidderName, gdprPrivacyPolicy gdprPrivacy.Policy) bool { - gdprSignal := gdpr.SignalAmbiguous - if signal, err := gdpr.SignalParse(gdprPrivacyPolicy.Signal); err != nil { - gdprSignal = signal - } - - if canSync, err := a.gdprPerms.HostCookiesAllowed(ctx, gdprSignal, gdprPrivacyPolicy.Consent); err != nil || !canSync { - return false - } - canSync, err := a.gdprPerms.BidderSyncAllowed(ctx, bidder, gdprSignal, gdprPrivacyPolicy.Consent) - return canSync && err == nil -} - -// cache video bids only for Web -func cacheVideoOnly(bids pbs.PBSBidSlice, ctx context.Context, deps *auction, labels *metrics.Labels) error { - var cobjs []*pbc.CacheObject - for _, bid := range bids { - if bid.CreativeMediaType == "video" { - cobjs = append(cobjs, &pbc.CacheObject{ - Value: bid.Adm, - IsVideo: true, - }) - } - } - err := pbc.Put(ctx, cobjs) - if err != nil { - return err - } - videoIndex := 0 - for _, bid := range bids { - if bid.CreativeMediaType == "video" { - bid.CacheID = cobjs[videoIndex].UUID - bid.CacheURL = deps.cfg.GetCachedAssetURL(bid.CacheID) - bid.NURL = "" - bid.Adm = "" - videoIndex++ - } - } - return nil -} - -// checkForValidBidSize goes through list of bids & find those which are banner mediaType and with height or width not defined -// determine the num of ad unit sizes that were used in corresponding bid request -// if num_adunit_sizes == 1, assign the height and/or width to bid's height/width -// if num_adunit_sizes > 1, reject the bid (remove from list) and return an error -// return updated bid list object for next steps in auction -func checkForValidBidSize(bids pbs.PBSBidSlice, bidder *pbs.PBSBidder) pbs.PBSBidSlice { - finalValidBids := make([]*pbs.PBSBid, len(bids)) - finalBidCounter := 0 -bidLoop: - for _, bid := range bids { - if isUndimensionedBanner(bid) { - for _, adunit := range bidder.AdUnits { - if copyBannerDimensions(&adunit, bid, finalValidBids, &finalBidCounter) { - continue bidLoop - } - } - } else { - finalValidBids[finalBidCounter] = bid - finalBidCounter = finalBidCounter + 1 - } - } - return finalValidBids[:finalBidCounter] -} - -func isUndimensionedBanner(bid *pbs.PBSBid) bool { - return bid.CreativeMediaType == "banner" && (bid.Height == 0 || bid.Width == 0) -} - -func copyBannerDimensions(adunit *pbs.PBSAdUnit, bid *pbs.PBSBid, finalValidBids []*pbs.PBSBid, finalBidCounter *int) bool { - var bidIDEqualsCode bool = false - - if adunit.BidID == bid.BidID && adunit.Code == bid.AdUnitCode && adunit.Sizes != nil { - if len(adunit.Sizes) == 1 { - bid.Width, bid.Height = adunit.Sizes[0].W, adunit.Sizes[0].H - finalValidBids[*finalBidCounter] = bid - *finalBidCounter += 1 - } else if len(adunit.Sizes) > 1 { - glog.Warningf("Bid was rejected for bidder %s because no size was defined", bid.BidderCode) - } - bidIDEqualsCode = true - } - - return bidIDEqualsCode -} - -// sortBidsAddKeywordsMobile sorts the bids and adds ad server targeting keywords to each bid. -// The bids are sorted by cpm to find the highest bid. -// The ad server targeting keywords are added to all bids, with specific keywords for the highest bid. -func sortBidsAddKeywordsMobile(bids pbs.PBSBidSlice, pbs_req *pbs.PBSRequest, priceGranularitySetting string) { - if priceGranularitySetting == "" { - priceGranularitySetting = defaultPriceGranularity - } - - // record bids by ad unit code for sorting - code_bids := make(map[string]pbs.PBSBidSlice, len(bids)) - for _, bid := range bids { - code_bids[bid.AdUnitCode] = append(code_bids[bid.AdUnitCode], bid) - } - - // loop through ad units to find top bid - for _, unit := range pbs_req.AdUnits { - bar := code_bids[unit.Code] - - if len(bar) == 0 { - if glog.V(3) { - glog.Infof("No bids for ad unit '%s'", unit.Code) - } - continue - } - sort.Sort(bar) - - // after sorting we need to add the ad targeting keywords - for i, bid := range bar { - // We should eventually check for the error and do something. - roundedCpm := exchange.GetPriceBucket(bid.Price, openrtb_ext.PriceGranularityFromString(priceGranularitySetting)) - - hbSize := "" - if bid.Width != 0 && bid.Height != 0 { - width := strconv.FormatInt(bid.Width, 10) - height := strconv.FormatInt(bid.Height, 10) - hbSize = width + "x" + height - } - - hbPbBidderKey := string(openrtb_ext.HbpbConstantKey) + "_" + bid.BidderCode - hbBidderBidderKey := string(openrtb_ext.HbBidderConstantKey) + "_" + bid.BidderCode - hbCacheIDBidderKey := string(openrtb_ext.HbCacheKey) + "_" + bid.BidderCode - hbDealIDBidderKey := string(openrtb_ext.HbDealIDConstantKey) + "_" + bid.BidderCode - hbSizeBidderKey := string(openrtb_ext.HbSizeConstantKey) + "_" + bid.BidderCode - if pbs_req.MaxKeyLength != 0 { - hbPbBidderKey = hbPbBidderKey[:min(len(hbPbBidderKey), int(pbs_req.MaxKeyLength))] - hbBidderBidderKey = hbBidderBidderKey[:min(len(hbBidderBidderKey), int(pbs_req.MaxKeyLength))] - hbCacheIDBidderKey = hbCacheIDBidderKey[:min(len(hbCacheIDBidderKey), int(pbs_req.MaxKeyLength))] - hbDealIDBidderKey = hbDealIDBidderKey[:min(len(hbDealIDBidderKey), int(pbs_req.MaxKeyLength))] - hbSizeBidderKey = hbSizeBidderKey[:min(len(hbSizeBidderKey), int(pbs_req.MaxKeyLength))] - } - - // fixes #288 where map was being overwritten instead of updated - if bid.AdServerTargeting == nil { - bid.AdServerTargeting = make(map[string]string) - } - kvs := bid.AdServerTargeting - - kvs[hbPbBidderKey] = roundedCpm - kvs[hbBidderBidderKey] = bid.BidderCode - kvs[hbCacheIDBidderKey] = bid.CacheID - - if hbSize != "" { - kvs[hbSizeBidderKey] = hbSize - } - if bid.DealId != "" { - kvs[hbDealIDBidderKey] = bid.DealId - } - // For the top bid, we want to add the following additional keys - if i == 0 { - kvs[string(openrtb_ext.HbpbConstantKey)] = roundedCpm - kvs[string(openrtb_ext.HbBidderConstantKey)] = bid.BidderCode - kvs[string(openrtb_ext.HbCacheKey)] = bid.CacheID - if bid.DealId != "" { - kvs[string(openrtb_ext.HbDealIDConstantKey)] = bid.DealId - } - if hbSize != "" { - kvs[string(openrtb_ext.HbSizeConstantKey)] = hbSize - } - } - } - } -} - -func getDefaultLabels(r *http.Request) metrics.Labels { - return metrics.Labels{ - Source: metrics.DemandUnknown, - RType: metrics.ReqTypeLegacy, - PubID: "", - CookieFlag: metrics.CookieFlagUnknown, - RequestStatus: metrics.RequestStatusOK, - } -} - -func setLabelSource(labels *metrics.Labels, req *pbs.PBSRequest, status *string) { - if req.App != nil { - labels.Source = metrics.DemandApp - } else { - labels.Source = metrics.DemandWeb - if req.Cookie.HasAnyLiveSyncs() { - labels.CookieFlag = metrics.CookieFlagYes - } else { - labels.CookieFlag = metrics.CookieFlagNo - *status = "no_cookie" - } - } -} - -func cacheAccordingToMarkup(req *pbs.PBSRequest, resp *pbs.PBSResponse, ctx context.Context, a *auction, labels *metrics.Labels) error { - if req.CacheMarkup == 1 { - cobjs := make([]*pbc.CacheObject, len(resp.Bids)) - for i, bid := range resp.Bids { - if bid.CreativeMediaType == "video" { - cobjs[i] = &pbc.CacheObject{ - Value: bid.Adm, - IsVideo: true, - } - } else { - cobjs[i] = &pbc.CacheObject{ - Value: &pbc.BidCache{ - Adm: bid.Adm, - NURL: bid.NURL, - Width: bid.Width, - Height: bid.Height, - }, - IsVideo: false, - } - } - } - if err := pbc.Put(ctx, cobjs); err != nil { - return err - } - for i, bid := range resp.Bids { - bid.CacheID = cobjs[i].UUID - bid.CacheURL = a.cfg.GetCachedAssetURL(bid.CacheID) - bid.NURL = "" - bid.Adm = "" - } - } else if req.CacheMarkup == 2 { - return cacheVideoOnly(resp.Bids, ctx, a, labels) - } - return nil -} - -func processBidResult(bidList pbs.PBSBidSlice, bidder *pbs.PBSBidder, aLabels *metrics.AdapterLabels, metricsEngine metrics.MetricsEngine, err error) { - if err != nil { - var s struct{} - if err == context.DeadlineExceeded { - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorTimeout: s} - bidder.Error = "Timed out" - } else if err != context.Canceled { - bidder.Error = err.Error() - switch err.(type) { - case *errortypes.BadInput: - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorBadInput: s} - case *errortypes.BadServerResponse: - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorBadServerResponse: s} - default: - glog.Warningf("Error from bidder %v. Ignoring all bids: %v", bidder.BidderCode, err) - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorUnknown: s} - } - } - } else if bidList != nil { - bidList = checkForValidBidSize(bidList, bidder) - bidder.NumBids = len(bidList) - for _, bid := range bidList { - var cpm = float64(bid.Price * 1000) - metricsEngine.RecordAdapterPrice(*aLabels, cpm) - switch bid.CreativeMediaType { - case "banner": - metricsEngine.RecordAdapterBidReceived(*aLabels, openrtb_ext.BidTypeBanner, bid.Adm != "") - case "video": - metricsEngine.RecordAdapterBidReceived(*aLabels, openrtb_ext.BidTypeVideo, bid.Adm != "") - } - bid.ResponseTime = bidder.ResponseTime - } - } else { - bidder.NoBid = true - aLabels.AdapterBids = metrics.AdapterBidNone - } -} - -func (a *auction) recordMetrics(req *pbs.PBSRequest, labels metrics.Labels) { - a.metricsEngine.RecordRequest(labels) - if req == nil { - a.metricsEngine.RecordLegacyImps(labels, 0) - return - } - a.metricsEngine.RecordLegacyImps(labels, len(req.AdUnits)) - a.metricsEngine.RecordRequestTime(labels, time.Since(req.Start)) -} - -func (a *auction) processUserSync(req *pbs.PBSRequest, bidder *pbs.PBSBidder, blabels metrics.AdapterLabels, ex adapters.Adapter, ctx *context.Context) bool { - var skip bool = false - if req.App != nil { - return skip - } - // If exchanges[bidderCode] exists, then a.syncers[bidderCode] exists *except for districtm*. - // OpenRTB handles aliases differently, so this hack will keep legacy code working. For all other - // bidderCodes, a.syncers[bidderCode] will exist if exchanges[bidderCode] also does. - // This is guaranteed by the TestSyncers unit test inside usersync/usersync_test.go, which compares these maps to the (source of truth) openrtb_ext.BidderMap: - syncerCode := bidder.BidderCode - if syncerCode == "districtm" { - syncerCode = "appnexus" - } - syncer := a.syncersByBidder[syncerCode] - uid, _, _ := req.Cookie.GetUID(syncer.Key()) - if uid == "" { - bidder.NoCookie = true - privacyPolicies := privacy.Policies{ - GDPR: gdprPrivacy.Policy{ - Signal: req.ParseGDPR(), - Consent: req.ParseConsent(), - }, - } - if a.shouldUsersync(*ctx, openrtb_ext.BidderName(syncerCode), privacyPolicies.GDPR) { - sync, err := syncer.GetSync(allSyncTypes, privacyPolicies) - if err == nil { - bidder.UsersyncInfo = &pbs.UsersyncInfo{ - URL: sync.URL, - Type: string(sync.Type), - SupportCORS: sync.SupportCORS, - } - } else { - glog.Errorf("Failed to get usersync info for %s: %v", syncerCode, err) - } - } - blabels.CookieFlag = metrics.CookieFlagNo - if ex.SkipNoCookies() { - skip = true - } - } - return skip -} diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go deleted file mode 100644 index 1a30e025faa..00000000000 --- a/endpoints/auction_test.go +++ /dev/null @@ -1,654 +0,0 @@ -package endpoints - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/prebid_cache_client" - gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/prebid/prebid-server/usersync" - "github.com/spf13/viper" - - "github.com/stretchr/testify/assert" -) - -func TestSortBidsAndAddKeywordsForMobile(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":"F", - "buyeruid":"test_buyeruid", - "yob":2000, - "id":"testid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"test_adunitcode" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.1" - }, - "sdk":{ - "version":"0.0.1", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := pbs.ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Errorf("Unexpected error on parsing %v", err) - } - - bids := make(pbs.PBSBidSlice, 0) - - fb_bid := pbs.PBSBid{ - BidID: "test_bidid", - AdUnitCode: "test_adunitcode", - BidderCode: "audienceNetwork", - Price: 2.00, - Adm: "test_adm", - Width: 300, - Height: 250, - CacheID: "test_cache_id1", - DealId: "2345", - } - bids = append(bids, &fb_bid) - an_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "appnexus", - Price: 1.00, - Adm: "test_adm", - Width: 320, - Height: 50, - CacheID: "test_cache_id2", - DealId: "1234", - } - bids = append(bids, &an_bid) - rb_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "rubicon", - Price: 1.00, - Adm: "test_adm", - Width: 300, - Height: 250, - CacheID: "test_cache_id2", - DealId: "7890", - } - rb_bid.AdServerTargeting = map[string]string{ - "rpfl_1001": "15_tier0100", - } - bids = append(bids, &rb_bid) - nosize_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "nosizebidder", - Price: 1.00, - Adm: "test_adm", - CacheID: "test_cache_id2", - } - bids = append(bids, &nosize_bid) - nodeal_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "nodeal", - Price: 1.00, - Adm: "test_adm", - CacheID: "test_cache_id2", - } - bids = append(bids, &nodeal_bid) - pbs_resp := pbs.PBSResponse{ - Bids: bids, - } - sortBidsAddKeywordsMobile(pbs_resp.Bids, pbs_req, "") - - for _, bid := range bids { - if bid.AdServerTargeting == nil { - t.Error("Ad server targeting should not be nil") - } - if bid.BidderCode == "audienceNetwork" { - if bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)] != "300x250" { - t.Error(string(openrtb_ext.HbSizeConstantKey) + " key was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)] != "2.00" { - t.Error(string(openrtb_ext.HbpbConstantKey)+" key was not parsed correctly ", bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)]) - } - - if bid.AdServerTargeting[string(openrtb_ext.HbCacheKey)] != "test_cache_id1" { - t.Error(string(openrtb_ext.HbCacheKey) + " key was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbBidderConstantKey)] != "audienceNetwork" { - t.Error(string(openrtb_ext.HbBidderConstantKey) + " key was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)] != "2345" { - t.Error(string(openrtb_ext.HbDealIDConstantKey) + " key was not parsed correctly ") - } - } - if bid.BidderCode == "appnexus" { - if bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)+"_appnexus"] != "320x50" { - t.Error(string(openrtb_ext.HbSizeConstantKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbCacheKey)+"_appnexus"] != "test_cache_id2" { - t.Error(string(openrtb_ext.HbCacheKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbBidderConstantKey)+"_appnexus"] != "appnexus" { - t.Error(string(openrtb_ext.HbBidderConstantKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)+"_appnexus"] != "1.00" { - t.Error(string(openrtb_ext.HbpbConstantKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)] != "" { - t.Error(string(openrtb_ext.HbpbConstantKey) + " key was parsed for two bidders") - } - if bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_appnexus"] != "1234" { - t.Errorf(string(openrtb_ext.HbDealIDConstantKey)+"_appnexus was not parsed correctly %v", bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_appnexus"]) - } - } - if bid.BidderCode == string(openrtb_ext.BidderRubicon) { - if bid.AdServerTargeting["rpfl_1001"] != "15_tier0100" { - t.Error("custom ad_server_targeting KVPs from adapter were not preserved") - } - } - if bid.BidderCode == "nosizebidder" { - if _, exists := bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)+"_nosizebidder"]; exists { - t.Error(string(openrtb_ext.HbSizeConstantKey)+" key for nosize bidder was not parsed correctly", bid.AdServerTargeting) - } - } - if bid.BidderCode == "nodeal" { - if _, exists := bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_nodeal"]; exists { - t.Error(string(openrtb_ext.HbDealIDConstantKey) + " key for nodeal bidder was not parsed correctly") - } - } - } -} - -var ( - MaxValueLength = 1024 * 10 - MaxNumValues = 10 -) - -type responseObject struct { - UUID string `json:"uuid"` -} - -type response struct { - Responses []responseObject `json:"responses"` -} - -type putAnyObject struct { - Type string `json:"type"` - Value json.RawMessage `json:"value"` -} - -type putAnyRequest struct { - Puts []putAnyObject `json:"puts"` -} - -func DummyPrebidCacheServer(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read the request body.", http.StatusBadRequest) - return - } - defer r.Body.Close() - var put putAnyRequest - - err = json.Unmarshal(body, &put) - if err != nil { - http.Error(w, "Request body "+string(body)+" is not valid JSON.", http.StatusBadRequest) - return - } - - if len(put.Puts) > MaxNumValues { - http.Error(w, fmt.Sprintf("More keys than allowed: %d", MaxNumValues), http.StatusBadRequest) - return - } - - resp := response{ - Responses: make([]responseObject, len(put.Puts)), - } - for i, p := range put.Puts { - resp.Responses[i].UUID = fmt.Sprintf("UUID-%d", i+1) // deterministic for testing - if len(p.Value) > MaxValueLength { - http.Error(w, fmt.Sprintf("Value is larger than allowed size: %d", MaxValueLength), http.StatusBadRequest) - return - } - if len(p.Value) == 0 { - http.Error(w, "Missing value.", http.StatusBadRequest) - return - } - if p.Type != "xml" && p.Type != "json" { - http.Error(w, fmt.Sprintf("Type must be one of [\"json\", \"xml\"]. Found %v", p.Type), http.StatusBadRequest) - return - } - } - - b, err := json.Marshal(&resp) - if err != nil { - http.Error(w, "Failed to serialize UUIDs into JSON.", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Write(b) -} - -func TestCacheVideoOnly(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer)) - defer server.Close() - - bids := make(pbs.PBSBidSlice, 0) - fbBid := pbs.PBSBid{ - BidID: "test_bidid0", - AdUnitCode: "test_adunitcode0", - BidderCode: "audienceNetwork", - Price: 2.00, - Adm: "fb_test_adm", - Width: 300, - Height: 250, - DealId: "2345", - CreativeMediaType: "video", - } - bids = append(bids, &fbBid) - anBid := pbs.PBSBid{ - BidID: "test_bidid1", - AdUnitCode: "test_adunitcode1", - BidderCode: "appnexus", - Price: 1.00, - Adm: "an_test_adm", - Width: 320, - Height: 50, - DealId: "1234", - CreativeMediaType: "banner", - } - bids = append(bids, &anBid) - rbBannerBid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode2", - BidderCode: "rubicon", - Price: 1.00, - Adm: "rb_banner_test_adm", - Width: 300, - Height: 250, - DealId: "7890", - CreativeMediaType: "banner", - } - bids = append(bids, &rbBannerBid) - rbVideoBid1 := pbs.PBSBid{ - BidID: "test_bidid3", - AdUnitCode: "test_adunitcode3", - BidderCode: "rubicon", - Price: 1.00, - Adm: "rb_video_test_adm1", - Width: 300, - Height: 250, - DealId: "7890", - CreativeMediaType: "video", - } - bids = append(bids, &rbVideoBid1) - rbVideoBid2 := pbs.PBSBid{ - BidID: "test_bidid4", - AdUnitCode: "test_adunitcode4", - BidderCode: "rubicon", - Price: 1.00, - Adm: "rb_video_test_adm2", - Width: 300, - Height: 250, - DealId: "7890", - CreativeMediaType: "video", - } - bids = append(bids, &rbVideoBid2) - - 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()) - } - syncersByBidder := map[string]usersync.Syncer{} - gdprPerms := gdpr.NewPermissions(context.Background(), config.GDPR{ - HostVendorID: 0, - }, nil, nil) - prebid_cache_client.InitPrebidCache(server.URL) - var labels = &metrics.Labels{} - if err := cacheVideoOnly(bids, ctx, &auction{cfg: cfg, syncersByBidder: syncersByBidder, gdprPerms: gdprPerms, metricsEngine: &metricsConf.NilMetricsEngine{}}, labels); err != nil { - t.Errorf("Prebid cache failed: %v \n", err) - return - } - if bids[0].CacheID != "UUID-1" { - t.Errorf("UUID was '%s', should have been 'UUID-1'", bids[0].CacheID) - } - if bids[1].CacheID != "" { - t.Errorf("UUID was '%s', should have been empty", bids[1].CacheID) - } - if bids[2].CacheID != "" { - t.Errorf("UUID was '%s', should have been empty", bids[2].CacheID) - } - if bids[3].CacheID != "UUID-2" { - t.Errorf("First object UUID was '%s', should have been 'UUID-2'", bids[3].CacheID) - } - if bids[4].CacheID != "UUID-3" { - t.Errorf("Second object UUID was '%s', should have been 'UUID-3'", bids[4].CacheID) - } -} - -func TestShouldUsersync(t *testing.T) { - tests := []struct { - description string - signal string - allowHostCookies bool - allowBidderSync bool - wantAllow bool - }{ - { - description: "Don't sync - GDPR on, host cookies disallows and bidder sync disallows", - signal: "1", - allowHostCookies: false, - allowBidderSync: false, - wantAllow: false, - }, - { - description: "Don't sync - GDPR on, host cookies disallows and bidder sync allows", - signal: "1", - allowHostCookies: false, - allowBidderSync: true, - wantAllow: false, - }, - { - description: "Don't sync - GDPR on, host cookies allows and bidder sync disallows", - signal: "1", - allowHostCookies: true, - allowBidderSync: false, - wantAllow: false, - }, - { - description: "Sync - GDPR on, host cookies allows and bidder sync allows", - signal: "1", - allowHostCookies: true, - allowBidderSync: true, - wantAllow: true, - }, - { - description: "Don't sync - invalid GDPR signal, host cookies disallows and bidder sync disallows", - signal: "2", - allowHostCookies: false, - allowBidderSync: false, - wantAllow: false, - }, - } - - for _, tt := range tests { - deps := auction{ - gdprPerms: &auctionMockPermissions{ - allowBidderSync: tt.allowBidderSync, - allowHostCookies: tt.allowHostCookies, - }, - } - gdprPrivacyPolicy := gdprPolicy.Policy{ - Signal: tt.signal, - } - - allow := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, gdprPrivacyPolicy) - assert.Equal(t, tt.wantAllow, allow, tt.description) - } -} - -type auctionMockPermissions struct { - allowBidderSync bool - allowHostCookies bool - allowBidRequest bool - passGeo bool - passID bool -} - -func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { - return m.allowHostCookies, nil -} - -func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { - return m.allowBidderSync, 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) { - bids := make(pbs.PBSBidSlice, 0) - // bid1 will be rejected due to undefined size when adunit has multiple sizes - bid1 := pbs.PBSBid{ - BidID: "test_bidid1", - AdUnitCode: "test_adunitcode1", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - // Width: 100, - // Height: 100, - CreativeMediaType: "banner", - } - bids = append(bids, &bid1) - // bid2 will be considered a normal ideal banner bid - bid2 := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode2", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - Width: 100, - Height: 100, - CreativeMediaType: "banner", - } - bids = append(bids, &bid2) - // bid3 will have it's dimensions set based on sizes defined in request - bid3 := pbs.PBSBid{ - BidID: "test_bidid3", - AdUnitCode: "test_adunitcode3", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - //Width: 200, - //Height: 200, - CreativeMediaType: "banner", - } - - bids = append(bids, &bid3) - - // bid4 will be ignored as it's a video creative type - bid4 := pbs.PBSBid{ - BidID: "test_bidid_video", - AdUnitCode: "test_adunitcode_video", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - //Width: 400, - //Height: 400, - CreativeMediaType: "video", - } - - bids = append(bids, &bid4) - - mybidder := pbs.PBSBidder{ - BidderCode: "randNetwork", - AdUnitCode: "test_adunitcode", - AdUnits: []pbs.PBSAdUnit{ - { - BidID: "test_bidid1", - Sizes: []openrtb2.Format{ - { - W: 350, - H: 250, - }, - { - W: 300, - H: 50, - }, - }, - Code: "test_adunitcode1", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid2", - Sizes: []openrtb2.Format{ - { - W: 100, - H: 100, - }, - }, - Code: "test_adunitcode2", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid3", - Sizes: []openrtb2.Format{ - { - W: 200, - H: 200, - }, - }, - Code: "test_adunitcode3", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid_video", - Sizes: []openrtb2.Format{ - { - W: 400, - H: 400, - }, - }, - Code: "test_adunitcode_video", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_VIDEO, - }, - }, - { - BidID: "test_bidid3", - Sizes: []openrtb2.Format{ - { - W: 150, - H: 150, - }, - }, - Code: "test_adunitcode_x", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid_y", - Sizes: []openrtb2.Format{ - { - W: 150, - H: 150, - }, - }, - Code: "test_adunitcode_3", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - }, - } - - bids = checkForValidBidSize(bids, &mybidder) - - testdata, _ := json.MarshalIndent(bids, "", " ") - if len(bids) != 3 { - t.Errorf("Detected returned bid list did not contain only 3 bid objects as expected.\nBelow is the contents of the bid list\n%v", string(testdata)) - } - - for _, bid := range bids { - if bid.BidID == "test_bidid3" { - if bid.Width == 0 && bid.Height == 0 { - t.Errorf("Detected the Width & Height attributes in test bidID %v were not set to the dimensions used from the mybidder object", bid.BidID) - } - } - } -} - -func TestWriteAuctionError(t *testing.T) { - recorder := httptest.NewRecorder() - writeAuctionError(recorder, "some error message", nil) - var resp pbs.PBSResponse - json.Unmarshal(recorder.Body.Bytes(), &resp) - - if len(resp.Bids) != 0 { - t.Error("Error responses should return no bids.") - } - if resp.Status != "some error message" { - t.Errorf("The response status should be the error message. Got: %s", resp.Status) - } - - if len(resp.BidderStatus) != 0 { - t.Errorf("Error responses shouldn't have any BidderStatus elements. Got %d", len(resp.BidderStatus)) - } -} - -func TestPanicRecovery(t *testing.T) { - testAuction := auction{ - cfg: nil, - syncersByBidder: nil, - gdprPerms: &auctionMockPermissions{ - allowBidderSync: false, - allowHostCookies: false, - }, - metricsEngine: &metricsConf.NilMetricsEngine{}, - } - panicker := func(bidder *pbs.PBSBidder, blables metrics.AdapterLabels) { - panic("panic!") - } - recovered := testAuction.recoverSafely(panicker) - recovered(nil, metrics.AdapterLabels{}) -} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 790a8d6482d..eb6f17f2359 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1572,15 +1572,6 @@ func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) } } -// parseUserID gets this user's ID for the host machine, if it exists. -func parseUserID(cfg *config.Configuration, httpReq *http.Request) (string, bool) { - if hostCookie, err := httpReq.Cookie(cfg.HostCookie.CookieName); hostCookie != nil && err == nil { - return hostCookie.Value, true - } else { - return "", false - } -} - // Write(return) errors to the client, if any. Returns true if errors were found. func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) bool { var rc bool = false diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index a259719ba8a..20fdd56e74b 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -25,7 +25,6 @@ import ( "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "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" @@ -3725,21 +3724,6 @@ func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []s return testStoredRequestData, testStoredImpData, nil } -var mockAccountData = map[string]json.RawMessage{ - "valid_acct": json.RawMessage(`{"disabled":false}`), -} - -type mockAccountFetcher struct { -} - -func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { - if account, ok := mockAccountData[accountID]; ok { - return account, nil - } else { - return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} - } -} - type mockExchange struct { lastRequest *openrtb2.BidRequest } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 3163cd9d323..b7eb0b27c0b 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1435,19 +1435,3 @@ var testVideoStoredImpData = map[string]json.RawMessage{ var testVideoStoredRequestData = map[string]json.RawMessage{ "80ce30c53c16e6ede735f123ef6e32361bfc7b22": json.RawMessage(`{"accountid": "11223344", "site": {"page": "mygame.foo.com"}}`), } - -func loadValidRequest(t *testing.T) *openrtb_ext.BidRequestVideo { - reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json") - if err != nil { - t.Fatalf("Failed to fetch a valid request: %v", err) - } - - reqBody := getRequestPayload(t, reqData) - - reqVideo := &openrtb_ext.BidRequestVideo{} - if err := json.Unmarshal(reqBody, reqVideo); err != nil { - t.Fatalf("Failed to unmarshal the request: %v", err) - } - - return reqVideo -} diff --git a/exchange/auction.go b/exchange/auction.go index 94e808801d9..c8aff684e41 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -311,13 +311,6 @@ func valOrZero(useVal bool, val int) int { return 0 } -func maybeMake(shouldMake bool, capacity int) []prebid_cache_client.Cacheable { - if shouldMake { - return make([]prebid_cache_client.Cacheable, 0, capacity) - } - return nil -} - func cacheTTL(impTTL int64, bidTTL int64, defTTL int64, buffer int64) (ttl int64) { if impTTL <= 0 && bidTTL <= 0 { // Only use default if there is no imp nor bid TTL provided. We don't want the default diff --git a/exchange/auction_test.go b/exchange/auction_test.go index ee064fcb6f1..455ae5018e8 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -555,12 +555,6 @@ type pbsBid struct { Bidder openrtb_ext.BidderName `json:"bidder"` } -type cacheComparator struct { - freq int - expectedKeys []string - actualKeys []string -} - type mockCache struct { scheme string host string diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index da31658e32d..82f058514f7 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -1795,7 +1795,6 @@ func (bidder *mixedMultiBidder) MakeBids(internalRequest *openrtb2.BidRequest, e } type bidRejector struct { - httpRequest *adapters.RequestData httpResponse *adapters.ResponseData } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 9dcf9d66a7f..b4fd270d023 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2997,7 +2997,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) + bidCategory, _, rejections, err := applyCategoryMapping(nil, &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") diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 8991a116624..48e054c7bda 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,17 +8,14 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/gdpr" - - metricsConf "github.com/prebid/prebid-server/metrics/config" metricsConfig "github.com/prebid/prebid-server/metrics/config" - - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -88,7 +85,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op ex := &exchange{ adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), - me: &metricsConf.NilMetricsEngine{}, + me: &metricsConfig.NilMetricsEngine{}, cache: &wellBehavedCache{}, cacheTime: time.Duration(0), gDPR: gdpr.AlwaysAllow{}, @@ -129,14 +126,6 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op return buildBidMap(bidResp.SeatBid, len(mockBids)) } -func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb2.Bid) []openrtb_ext.BidderName { - bidders := make([]openrtb_ext.BidderName, 0, len(bids)) - for name := range bids { - bidders = append(bidders, name) - } - return bidders -} - func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]adaptedBidder { adapterMap := make(map[openrtb_ext.BidderName]adaptedBidder, len(bids)) for bidder, bids := range bids { diff --git a/go.mod b/go.mod index 347f36bcf86..e673d2218c7 100644 --- a/go.mod +++ b/go.mod @@ -8,20 +8,17 @@ require ( github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect - github.com/blang/semver v3.5.1+incompatible 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 github.com/docker/go-units v0.4.0 - github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/influxdata/influxdb v1.6.1 github.com/julienschmidt/httprouter v1.1.0 github.com/lib/pq v1.0.0 - github.com/magiconair/properties v1.8.5 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 diff --git a/go.sum b/go.sum index 31e6b8fc93f..bcab99e4ebf 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -87,8 +85,6 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= diff --git a/main.go b/main.go index 6087c3d69dd..76fa64f77ef 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" - pbc "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/router" "github.com/prebid/prebid-server/server" "github.com/prebid/prebid-server/util/task" @@ -56,8 +55,6 @@ func serve(cfg *config.Configuration) error { return err } - pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL()) - corsRouter := router.SupportCORS(r) server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(currencyConverter, fetchingInterval), r.MetricsEngine) diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 51ba8cafe2f..66849db5864 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -90,13 +90,6 @@ func (me *MultiMetricsEngine) RecordImps(implabels metrics.ImpLabels) { } } -// RecordImps for the legacy endpoint -func (me *MultiMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { - for _, thisME := range *me { - thisME.RecordLegacyImps(labels, numImps) - } -} - // RecordRequestTime across all engines func (me *MultiMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { for _, thisME := range *me { @@ -278,10 +271,6 @@ func (me *NilMetricsEngine) RecordConnectionClose(success bool) { func (me *NilMetricsEngine) RecordImps(implabels metrics.ImpLabels) { } -// RecordLegacyImps as a noop -func (me *NilMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { -} - // RecordRequestTime as a noop func (me *NilMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { } diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 0d6bcdb922d..e4227afda77 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -78,7 +78,6 @@ func TestMultiMetricsEngine(t *testing.T) { for i := 0; i < 5; i++ { metricsEngine.RecordRequest(labels) metricsEngine.RecordImps(impTypeLabels) - metricsEngine.RecordLegacyImps(labels, 2) metricsEngine.RecordRequestTime(labels, time.Millisecond*20) metricsEngine.RecordAdapterRequest(pubLabels) metricsEngine.RecordAdapterRequest(apnLabels) @@ -147,7 +146,6 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "Request", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusOK].Count(), 5) VerifyMetrics(t, "ImpMeter", goEngine.ImpMeter.Count(), 8) - VerifyMetrics(t, "LegacyImpMeter", goEngine.LegacyImpMeter.Count(), 10) VerifyMetrics(t, "NoCookieMeter", goEngine.NoCookieMeter.Count(), 0) VerifyMetrics(t, "AdapterMetrics.Pubmatic.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].GotBidsMeter.Count(), 5) VerifyMetrics(t, "AdapterMetrics.Pubmatic.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].NoBidMeter.Count(), 0) diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 615b83b8be9..c93f10602c7 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -18,7 +18,6 @@ type Metrics struct { ConnectionAcceptErrorMeter metrics.Meter ConnectionCloseErrorMeter metrics.Meter ImpMeter metrics.Meter - LegacyImpMeter metrics.Meter AppRequestMeter metrics.Meter NoCookieMeter metrics.Meter RequestTimer metrics.Timer @@ -101,9 +100,6 @@ type accountMetrics struct { adapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics } -// Defining an "unknown" bidder -const unknownBidder openrtb_ext.BidderName = "unknown" - // NewBlankMetrics creates a new Metrics object with all blank metrics object. This may also be useful for // testing routines to ensure that no metrics are written anywhere. // @@ -122,7 +118,6 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa ConnectionAcceptErrorMeter: blankMeter, ConnectionCloseErrorMeter: blankMeter, ImpMeter: blankMeter, - LegacyImpMeter: blankMeter, AppRequestMeter: blankMeter, NoCookieMeter: blankMeter, RequestTimer: blankTimer, @@ -216,7 +211,6 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.ConnectionAcceptErrorMeter = metrics.GetOrRegisterMeter("connection_accept_errors", registry) newMetrics.ConnectionCloseErrorMeter = metrics.GetOrRegisterMeter("connection_close_errors", registry) newMetrics.ImpMeter = metrics.GetOrRegisterMeter("imps_requested", registry) - newMetrics.LegacyImpMeter = metrics.GetOrRegisterMeter("legacy_imps_requested", registry) newMetrics.ImpsTypeBanner = metrics.GetOrRegisterMeter("imp_banner", registry) newMetrics.ImpsTypeVideo = metrics.GetOrRegisterMeter("imp_video", registry) @@ -452,10 +446,6 @@ func (me *Metrics) RecordImps(labels ImpLabels) { } } -func (me *Metrics) RecordLegacyImps(labels Labels, numImps int) { - me.LegacyImpMeter.Mark(int64(numImps)) -} - func (me *Metrics) RecordConnectionAccept(success bool) { if success { me.ConnectionCounter.Inc(1) diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 346a64a737f..dd2430d6b74 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -36,10 +36,6 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "prebid_cache_request_time.ok", m.PrebidCacheRequestTimerSuccess) ensureContains(t, registry, "prebid_cache_request_time.err", m.PrebidCacheRequestTimerError) - ensureContains(t, registry, "requests.ok.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusOK]) - ensureContains(t, registry, "requests.badinput.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusBadInput]) - ensureContains(t, registry, "requests.err.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusErr]) - ensureContains(t, registry, "requests.networkerr.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusNetworkErr]) ensureContains(t, registry, "requests.ok.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusOK]) ensureContains(t, registry, "requests.badinput.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusBadInput]) ensureContains(t, registry, "requests.err.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusErr]) diff --git a/metrics/metrics.go b/metrics/metrics.go index af45f9b4f5a..9d16143f0d4 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -382,7 +382,6 @@ type MetricsEngine interface { RecordConnectionClose(success bool) RecordRequest(labels Labels) // ignores adapter. only statusOk and statusErr fom status RecordImps(labels ImpLabels) // RecordImps across openRTB2 engines that support the 'Native' Imp Type - RecordLegacyImps(labels Labels, numImps int) // RecordImps for the legacy engine RecordRequestTime(labels Labels, length time.Duration) // ignores adapter. only statusOk and statusErr fom status RecordAdapterRequest(labels AdapterLabels) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index b8ab23b768a..c8d5311c3a4 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -32,11 +32,6 @@ func (me *MetricsEngineMock) RecordImps(labels ImpLabels) { me.Called(labels) } -// RecordLegacyImps mock -func (me *MetricsEngineMock) RecordLegacyImps(labels Labels, numImps int) { - me.Called(labels, numImps) -} - // RecordRequestTime mock func (me *MetricsEngineMock) RecordRequestTime(labels Labels, length time.Duration) { me.Called(labels, length) diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 52470369094..53a5afabd53 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -476,10 +476,6 @@ func (m *Metrics) RecordImps(labels metrics.ImpLabels) { }).Inc() } -func (m *Metrics) RecordLegacyImps(labels metrics.Labels, numImps int) { - m.impressionsLegacy.Add(float64(numImps)) -} - func (m *Metrics) RecordRequestTime(labels metrics.Labels, length time.Duration) { if labels.RequestStatus == metrics.RequestStatusOK { m.requestsTimer.With(prometheus.Labels{ diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 0fe852b81df..fc8abdb9d04 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -355,16 +355,6 @@ func TestImpressionsMetric(t *testing.T) { } } -func TestLegacyImpressionsMetric(t *testing.T) { - m := createMetricsForTesting() - - m.RecordLegacyImps(metrics.Labels{}, 42) - - expectedCount := float64(42) - assertCounterValue(t, "", "impressionsLegacy", m.impressionsLegacy, - expectedCount) -} - func TestRequestTimeMetric(t *testing.T) { requestType := metrics.ReqTypeORTB2Web performTest := func(m *Metrics, requestStatus metrics.RequestStatus, timeInMs float64) { @@ -1162,18 +1152,6 @@ func TestPrebidCacheRequestTimeMetric(t *testing.T) { assertHistogram(t, "Error", errorResult, errorExpectedCount, errorExpectedSum) } -func TestMetricAccumulationSpotCheck(t *testing.T) { - m := createMetricsForTesting() - - m.RecordLegacyImps(metrics.Labels{}, 1) - m.RecordLegacyImps(metrics.Labels{}, 2) - m.RecordLegacyImps(metrics.Labels{}, 3) - - expectedValue := float64(1 + 2 + 3) - assertCounterValue(t, "", "impressionsLegacy", m.impressionsLegacy, - expectedValue) -} - func TestRecordRequestQueueTimeMetric(t *testing.T) { performTest := func(m *Metrics, requestStatus bool, requestType metrics.RequestType, timeInSec float64) { m.RecordRequestQueueTime(requestStatus, requestType, time.Duration(timeInSec*float64(time.Second))) diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 4e2d1ff5ba1..d99f2f6f39b 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -104,15 +104,6 @@ func setUidStatusesAsString() []string { return valuesAsString } -func storedDataTypesAsString() []string { - values := metrics.StoredDataTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - func storedDataFetchTypesAsString() []string { values := metrics.StoredDataFetchTypes() valuesAsString := make([]string, len(values)) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 7976451877c..14a261e314e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -12,8 +12,6 @@ import ( "github.com/xeipuuv/gojsonschema" ) -const schemaDirectory = "static/bidder-params" - // BidderName refers to a core bidder id or an alias id. type BidderName string diff --git a/openrtb_ext/imp_beachfront.go b/openrtb_ext/imp_beachfront.go index 104ca8f9fb7..9e65d54b82b 100644 --- a/openrtb_ext/imp_beachfront.go +++ b/openrtb_ext/imp_beachfront.go @@ -4,10 +4,10 @@ type ExtImpBeachfront struct { AppId string `json:"appId"` AppIds ExtImpBeachfrontAppIds `json:"appIds"` BidFloor float64 `json:"bidfloor"` - VideoResponseType string `json:"videoResponseType, omitempty"` + VideoResponseType string `json:"videoResponseType,omitempty"` } type ExtImpBeachfrontAppIds struct { - Video string `json:"video, omitempty"` - Banner string `json:"banner, omitempty"` + Video string `json:"video,omitempty"` + Banner string `json:"banner,omitempty"` } diff --git a/openrtb_ext/imp_nanointeractive.go b/openrtb_ext/imp_nanointeractive.go index 28db5be0d07..77e386237ac 100644 --- a/openrtb_ext/imp_nanointeractive.go +++ b/openrtb_ext/imp_nanointeractive.go @@ -3,8 +3,8 @@ package openrtb_ext // ExtImpNanoInteractive defines the contract for bidrequest.imp[i].ext.nanointeractive type ExtImpNanoInteractive struct { Pid string `json:"pid"` - Nq []string `json:"nq, omitempty"` - Category string `json:"category, omitempty"` - SubId string `json:"subId, omitempty"` - Ref string `json:"ref, omitempty"` + Nq []string `json:"nq,omitempty"` + Category string `json:"category,omitempty"` + SubId string `json:"subId,omitempty"` + Ref string `json:"ref,omitempty"` } diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go deleted file mode 100644 index c05b9a8c00d..00000000000 --- a/pbs/pbsrequest.go +++ /dev/null @@ -1,403 +0,0 @@ -package pbs - -import ( - "encoding/json" - "fmt" - "math/rand" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/prebid/prebid-server/cache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/httputil" - "github.com/prebid/prebid-server/util/iputil" - - "github.com/blang/semver" - "github.com/buger/jsonparser" - "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "golang.org/x/net/publicsuffix" -) - -const MAX_BIDDERS = 8 - -type MediaType byte - -const ( - MEDIA_TYPE_BANNER MediaType = iota - MEDIA_TYPE_VIDEO -) - -type ConfigCache interface { - LoadConfig(string) ([]Bids, error) -} - -type Bids struct { - BidderCode string `json:"bidder"` - BidID string `json:"bid_id"` - Params json.RawMessage `json:"params"` -} - -// Structure for holding video-specific information -type PBSVideo struct { - //Content MIME types supported. Popular MIME types may include “video/x-ms-wmv” for Windows Media and “video/x-flv” for Flash Video. - Mimes []string `json:"mimes,omitempty"` - - //Minimum video ad duration in seconds. - Minduration int64 `json:"minduration,omitempty"` - - // Maximum video ad duration in seconds. - Maxduration int64 `json:"maxduration,omitempty"` - - //Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. - Startdelay int64 `json:"startdelay,omitempty"` - - // Indicates if the player will allow the video to be skipped ( 0 = no, 1 = yes). - Skippable int `json:"skippable,omitempty"` - - // Playback method code Description - // 1 - Initiates on Page Load with Sound On - // 2 - Initiates on Page Load with Sound Off by Default - // 3 - Initiates on Click with Sound On - // 4 - Initiates on Mouse-Over with Sound On - // 5 - Initiates on Entering Viewport with Sound On - // 6 - Initiates on Entering Viewport with Sound Off by Default - PlaybackMethod int8 `json:"playback_method,omitempty"` - - //protocols as specified in ORTB 5.8 - // 1 VAST 1.0 - // 2 VAST 2.0 - // 3 VAST 3.0 - // 4 VAST 1.0 Wrapper - // 5 VAST 2.0 Wrapper - // 6 VAST 3.0 Wrapper - // 7 VAST 4.0 - // 8 VAST 4.0 Wrapper - // 9 DAAST 1.0 - // 10 DAAST 1.0 Wrapper - Protocols []int8 `json:"protocols,omitempty"` -} - -type AdUnit struct { - Code string `json:"code"` - TopFrame int8 `json:"is_top_frame"` - Sizes []openrtb2.Format `json:"sizes"` - Bids []Bids `json:"bids"` - ConfigID string `json:"config_id"` - MediaTypes []string `json:"media_types"` - Instl int8 `json:"instl"` - Video PBSVideo `json:"video"` -} - -type PBSAdUnit struct { - Sizes []openrtb2.Format - TopFrame int8 - Code string - BidID string - Params json.RawMessage - Video PBSVideo - MediaTypes []MediaType - Instl int8 -} - -func ParseMediaType(s string) (MediaType, error) { - mediaTypes := map[string]MediaType{"BANNER": MEDIA_TYPE_BANNER, "VIDEO": MEDIA_TYPE_VIDEO} - t, ok := mediaTypes[strings.ToUpper(s)] - if !ok { - return 0, fmt.Errorf("Invalid MediaType %s", s) - } - return t, nil -} - -type SDK struct { - Version string `json:"version"` - Source string `json:"source"` - Platform string `json:"platform"` -} - -type PBSBidder struct { - BidderCode string `json:"bidder"` - AdUnitCode string `json:"ad_unit,omitempty"` // for index to dedup responses - ResponseTime int `json:"response_time_ms,omitempty"` - NumBids int `json:"num_bids,omitempty"` - Error string `json:"error,omitempty"` - NoCookie bool `json:"no_cookie,omitempty"` - NoBid bool `json:"no_bid,omitempty"` - UsersyncInfo *UsersyncInfo `json:"usersync,omitempty"` - Debug []*BidderDebug `json:"debug,omitempty"` - - AdUnits []PBSAdUnit `json:"-"` -} - -type UsersyncInfo struct { - URL string `json:"url,omitempty"` - Type string `json:"type,omitempty"` - SupportCORS bool `json:"supportCORS,omitempty"` -} - -func (bidder *PBSBidder) LookupBidID(Code string) string { - for _, unit := range bidder.AdUnits { - if unit.Code == Code { - return unit.BidID - } - } - return "" -} - -func (bidder *PBSBidder) LookupAdUnit(Code string) (unit *PBSAdUnit) { - for _, unit := range bidder.AdUnits { - if unit.Code == Code { - return &unit - } - } - return nil -} - -type PBSRequest struct { - AccountID string `json:"account_id"` - Tid string `json:"tid"` - CacheMarkup int8 `json:"cache_markup"` - SortBids int8 `json:"sort_bids"` - MaxKeyLength int8 `json:"max_key_length"` - Secure int8 `json:"secure"` - TimeoutMillis int64 `json:"timeout_millis"` - AdUnits []AdUnit `json:"ad_units"` - IsDebug bool `json:"is_debug"` - App *openrtb2.App `json:"app"` - Device *openrtb2.Device `json:"device"` - PBSUser json.RawMessage `json:"user"` - SDK *SDK `json:"sdk"` - - // internal - Bidders []*PBSBidder `json:"-"` - User *openrtb2.User `json:"-"` - Cookie *usersync.Cookie `json:"-"` - Url string `json:"-"` - Domain string `json:"-"` - Regs *openrtb2.Regs `json:"-"` - Start time.Time -} - -func ConfigGet(cache cache.Cache, id string) ([]Bids, error) { - conf, err := cache.Config().Get(id) - if err != nil { - return nil, err - } - - bids := make([]Bids, 0) - err = json.Unmarshal([]byte(conf), &bids) - if err != nil { - return nil, err - } - - return bids, nil -} - -func ParseMediaTypes(types []string) []MediaType { - var mtypes []MediaType - mtmap := make(map[MediaType]bool) - - if types == nil { - mtypes = append(mtypes, MEDIA_TYPE_BANNER) - } else { - for _, t := range types { - mt, er := ParseMediaType(t) - if er != nil { - glog.Infof("Invalid media type: %s", er) - } else { - if !mtmap[mt] { - mtypes = append(mtypes, mt) - mtmap[mt] = true - } - } - } - if len(mtypes) == 0 { - mtypes = append(mtypes, MEDIA_TYPE_BANNER) - } - } - return mtypes -} - -var ipv4Validator iputil.IPValidator = iputil.VersionIPValidator{Version: iputil.IPv4} - -func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.Cache, hostCookieConfig *config.HostCookie) (*PBSRequest, error) { - defer r.Body.Close() - - pbsReq := &PBSRequest{} - err := json.NewDecoder(r.Body).Decode(&pbsReq) - if err != nil { - return nil, err - } - pbsReq.Start = time.Now() - - if len(pbsReq.AdUnits) == 0 { - return nil, fmt.Errorf("No ad units specified") - } - - pbsReq.TimeoutMillis = int64(cfg.LimitAuctionTimeout(time.Duration(pbsReq.TimeoutMillis)*time.Millisecond) / time.Millisecond) - - if pbsReq.Device == nil { - pbsReq.Device = &openrtb2.Device{} - } - if ip, _ := httputil.FindIP(r, ipv4Validator); ip != nil { - pbsReq.Device.IP = ip.String() - } - - if pbsReq.SDK == nil { - pbsReq.SDK = &SDK{} - } - - // Early versions of prebid mobile are sending requests with gender indicated by numbers, - // those traffic can't be parsed by latest Prebid Server after the change of gender to use string so clients using early versions can't be monetized. - // To handle those traffic, adding a check here to ignore the sent gender for versions lower than 0.0.2. - v1, err := semver.Make(pbsReq.SDK.Version) - v2, err := semver.Make("0.0.2") - if v1.Compare(v2) >= 0 && pbsReq.PBSUser != nil { - err = json.Unmarshal([]byte(pbsReq.PBSUser), &pbsReq.User) - if err != nil { - return nil, err - } - } - - if pbsReq.User == nil { - pbsReq.User = &openrtb2.User{} - } - - // use client-side data for web requests - if pbsReq.App == nil { - pbsReq.Cookie = usersync.ParseCookieFromRequest(r, hostCookieConfig) - - pbsReq.Device.UA = r.Header.Get("User-Agent") - - pbsReq.Url = r.Header.Get("Referer") // must be specified in the header - // TODO: this should explicitly put us in test mode - if r.FormValue("url_override") != "" { - pbsReq.Url = r.FormValue("url_override") - } - if strings.Index(pbsReq.Url, "http") == -1 { - pbsReq.Url = fmt.Sprintf("http://%s", pbsReq.Url) - } - - url, err := url.Parse(pbsReq.Url) - if err != nil { - return nil, fmt.Errorf("Invalid URL '%s': %v", pbsReq.Url, err) - } - - if url.Host == "" { - return nil, fmt.Errorf("Host not found from URL '%v'", url) - } - - pbsReq.Domain, err = publicsuffix.EffectiveTLDPlusOne(url.Host) - if err != nil { - return nil, fmt.Errorf("Invalid URL '%s': %v", url.Host, err) - } - } - - if r.FormValue("debug") == "1" { - pbsReq.IsDebug = true - } - - if httputil.IsSecure(r) { - pbsReq.Secure = 1 - } - - pbsReq.Bidders = make([]*PBSBidder, 0, MAX_BIDDERS) - - for _, unit := range pbsReq.AdUnits { - bidders := unit.Bids - if unit.ConfigID != "" { - bidders, err = ConfigGet(cache, unit.ConfigID) - if err != nil { - if _, notFound := err.(*stored_requests.NotFoundError); !notFound { - glog.Warningf("Failed to load config '%s' from cache: %v", unit.ConfigID, err) - } - // proceed with other ad units - continue - } - } - - if glog.V(2) { - glog.Infof("Ad unit %s has %d bidders for %d sizes", unit.Code, len(bidders), len(unit.Sizes)) - } - - mtypes := ParseMediaTypes(unit.MediaTypes) - for _, b := range bidders { - var bidder *PBSBidder - for _, pb := range pbsReq.Bidders { - if pb.BidderCode == b.BidderCode { - bidder = pb - } - } - - if bidder == nil { - bidder = &PBSBidder{BidderCode: b.BidderCode} - pbsReq.Bidders = append(pbsReq.Bidders, bidder) - } - if b.BidID == "" { - b.BidID = fmt.Sprintf("%d", rand.Int63()) - } - - pau := PBSAdUnit{ - Sizes: unit.Sizes, - TopFrame: unit.TopFrame, - Code: unit.Code, - Instl: unit.Instl, - Params: b.Params, - BidID: b.BidID, - MediaTypes: mtypes, - Video: unit.Video, - } - - bidder.AdUnits = append(bidder.AdUnits, pau) - } - } - - return pbsReq, nil -} - -func (req PBSRequest) Elapsed() int { - return int(time.Since(req.Start) / 1000000) -} - -func (p PBSRequest) String() string { - b, _ := json.MarshalIndent(p, "", " ") - return string(b) -} - -// parses the "Regs.ext.gdpr" from the request, if it exists. Otherwise returns an empty string. -func (req *PBSRequest) ParseGDPR() string { - if req == nil || req.Regs == nil || len(req.Regs.Ext) == 0 { - return "" - } - val, err := jsonparser.GetInt(req.Regs.Ext, "gdpr") - if err != nil { - return "" - } - gdpr := strconv.Itoa(int(val)) - - return gdpr -} - -// parses the "User.ext.consent" from the request, if it exists. Otherwise returns an empty string. -func (req *PBSRequest) ParseConsent() string { - if req == nil || req.User == nil { - return "" - } - return parseString(req.User.Ext, "consent") -} - -func parseString(data []byte, key string) string { - if len(data) == 0 { - return "" - } - val, err := jsonparser.GetString(data, key) - if err != nil { - return "" - } - return val -} diff --git a/pbs/pbsrequest_test.go b/pbs/pbsrequest_test.go deleted file mode 100644 index 52cd6153323..00000000000 --- a/pbs/pbsrequest_test.go +++ /dev/null @@ -1,735 +0,0 @@ -package pbs - -import ( - "bytes" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/magiconair/properties/assert" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" -) - -const mimeVideoMp4 = "video/mp4" -const mimeVideoFlv = "video/x-flv" - -func TestParseMediaTypes(t *testing.T) { - types1 := []string{"Banner"} - t1 := ParseMediaTypes(types1) - assert.Equal(t, len(t1), 1) - assert.Equal(t, t1[0], MEDIA_TYPE_BANNER) - - types2 := []string{"Banner", "Video"} - t2 := ParseMediaTypes(types2) - assert.Equal(t, len(t2), 2) - assert.Equal(t, t2[0], MEDIA_TYPE_BANNER) - assert.Equal(t, t2[1], MEDIA_TYPE_VIDEO) - - types3 := []string{"Banner", "Vo"} - t3 := ParseMediaTypes(types3) - assert.Equal(t, len(t3), 1) - assert.Equal(t, t3[0], MEDIA_TYPE_BANNER) -} - -func TestParseSimpleRequest(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ] - }, - { - "code": "second", - "sizes": [{"w": 728, "h": 90}], - "media_types" :["banner", "video"], - "video" : { - "mimes" : ["video/mp4", "video/x-flv"] - }, - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ] - } - - ] - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 2 { - t.Errorf("Parse ad units failed") - } - - // see if our internal representation is intact - if len(pbs_req.Bidders) != 2 { - t.Fatalf("Should have two bidders not %d", len(pbs_req.Bidders)) - } - if pbs_req.Bidders[0].BidderCode != "ix" { - t.Errorf("First bidder not index") - } - if len(pbs_req.Bidders[0].AdUnits) != 2 { - t.Errorf("Index bidder should have 2 ad unit") - } - if pbs_req.Bidders[1].BidderCode != "appnexus" { - t.Errorf("Second bidder not appnexus") - } - if len(pbs_req.Bidders[1].AdUnits) != 2 { - t.Errorf("AppNexus bidder should have 2 ad unit") - } - if pbs_req.Bidders[1].AdUnits[0].BidID == "" { - t.Errorf("ID should have been generated for empty BidID") - } - if pbs_req.AdUnits[1].MediaTypes[0] != "banner" { - t.Errorf("Instead of banner MediaType received %s", pbs_req.AdUnits[1].MediaTypes[0]) - } - if pbs_req.AdUnits[1].MediaTypes[1] != "video" { - t.Errorf("Instead of video MediaType received %s", pbs_req.AdUnits[1].MediaTypes[0]) - } - if pbs_req.AdUnits[1].Video.Mimes[0] != mimeVideoMp4 { - t.Errorf("Instead of video/mp4 mimes received %s", pbs_req.AdUnits[1].Video.Mimes) - } - if pbs_req.AdUnits[1].Video.Mimes[1] != mimeVideoFlv { - t.Errorf("Instead of video/flv mimes received %s", pbs_req.AdUnits[1].Video.Mimes) - } - -} - -func TestHeaderParsing(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bidders": [ - { - "bidder": "ix", - "params": { - "id": "417", - "siteID": "test-site" - } - } - ] - } - ] - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - r.Header.Add("User-Agent", "Mozilla/") - d, _ := dummycache.New() - hcc := config.HostCookie{} - - d.Config().Set("dummy", dummyConfig) - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed") - } - if pbs_req.Url != "http://nytimes.com/cool.html" { - t.Errorf("Failed to pull URL from referrer") - } - if pbs_req.Domain != "nytimes.com" { - t.Errorf("Failed to parse TLD from referrer: %s not nytimes.com", pbs_req.Domain) - } - if pbs_req.Device.UA != "Mozilla/" { - t.Errorf("Failed to pull User-Agent from referrer") - } -} - -var dummyConfig = ` -[ - { - "bidder": "ix", - "bid_id": "22222222", - "params": { - "id": "4", - "siteID": "186774", - "timeout": "10000" - } - - }, - { - "bidder": "audienceNetwork", - "bid_id": "22222225", - "params": { - } - }, - { - "bidder": "pubmatic", - "bid_id": "22222223", - "params": { - "publisherId": "156009", - "adSlot": "39620189@728x90" - } - }, - { - "bidder": "appnexus", - "bid_id": "22222224", - "params": { - "placementId": "1" - } - } - ] - ` - -func TestParseConfig(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ] - }, - { - "code": "second", - "sizes": [{"w": 728, "h": 90}], - "config_id": "abcd" - } - ] - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - d, _ := dummycache.New() - hcc := config.HostCookie{} - - d.Config().Set("dummy", dummyConfig) - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 2 { - t.Errorf("Parse ad units failed") - } - - // see if our internal representation is intact - if len(pbs_req.Bidders) != 4 { - t.Fatalf("Should have 4 bidders not %d", len(pbs_req.Bidders)) - } - if pbs_req.Bidders[0].BidderCode != "ix" { - t.Errorf("First bidder not index") - } - if len(pbs_req.Bidders[0].AdUnits) != 2 { - t.Errorf("Index bidder should have 1 ad unit") - } - if pbs_req.Bidders[1].BidderCode != "appnexus" { - t.Errorf("Second bidder not appnexus") - } - if len(pbs_req.Bidders[1].AdUnits) != 2 { - t.Errorf("AppNexus bidder should have 2 ad unit") - } -} - -func TestParseMobileRequestFirstVersion(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":0, - "buyeruid":"test_buyeruid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"5d748364ee9c46a2b112892fc3551b6f" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.1" - }, - "sdk":{ - "version":"0.0.1", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 1 { - t.Errorf("Parse ad units failed") - } - // We are expecting all user fields to be nil. We don't parse user on v0.0.1 of prebid mobile - if pbs_req.User.BuyerUID != "" { - t.Errorf("Parse user buyeruid failed %s", pbs_req.User.BuyerUID) - } - if pbs_req.User.Gender != "" { - t.Errorf("Parse user gender failed %s", pbs_req.User.Gender) - } - if pbs_req.User.Yob != 0 { - t.Errorf("Parse user year of birth failed %d", pbs_req.User.Yob) - } - if pbs_req.User.ID != "" { - t.Errorf("Parse user id failed %s", pbs_req.User.ID) - } - - if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { - t.Errorf("Parse app bundle failed") - } - if pbs_req.App.Ver != "0.0.1" { - t.Errorf("Parse app version failed") - } - - if pbs_req.Device.IFA != "test_device_ifa" { - t.Errorf("Parse device ifa failed") - } - if pbs_req.Device.OSV != "9.3.5" { - t.Errorf("Parse device osv failed") - } - if pbs_req.Device.OS != "iOS" { - t.Errorf("Parse device os failed") - } - if pbs_req.Device.Make != "Apple" { - t.Errorf("Parse device make failed") - } - if pbs_req.Device.Model != "iPhone6,1" { - t.Errorf("Parse device model failed") - } -} - -func TestParseMobileRequest(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":"F", - "buyeruid":"test_buyeruid", - "yob":2000, - "id":"testid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"5d748364ee9c46a2b112892fc3551b6f" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.2" - }, - "sdk":{ - "version":"0.0.2", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 1 { - t.Errorf("Parse ad units failed") - } - - if pbs_req.User.BuyerUID != "test_buyeruid" { - t.Errorf("Parse user buyeruid failed") - } - if pbs_req.User.Gender != "F" { - t.Errorf("Parse user gender failed") - } - if pbs_req.User.Yob != 2000 { - t.Errorf("Parse user year of birth failed") - } - if pbs_req.User.ID != "testid" { - t.Errorf("Parse user id failed") - } - if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { - t.Errorf("Parse app bundle failed") - } - if pbs_req.App.Ver != "0.0.2" { - t.Errorf("Parse app version failed") - } - - if pbs_req.Device.IFA != "test_device_ifa" { - t.Errorf("Parse device ifa failed") - } - if pbs_req.Device.OSV != "9.3.5" { - t.Errorf("Parse device osv failed") - } - if pbs_req.Device.OS != "iOS" { - t.Errorf("Parse device os failed") - } - if pbs_req.Device.Make != "Apple" { - t.Errorf("Parse device make failed") - } - if pbs_req.Device.Model != "iPhone6,1" { - t.Errorf("Parse device model failed") - } - if pbs_req.SDK.Version != "0.0.2" { - t.Errorf("Parse sdk version failed") - } - if pbs_req.SDK.Source != "prebid-mobile" { - t.Errorf("Parse sdk source failed") - } - if pbs_req.SDK.Platform != "iOS" { - t.Errorf("Parse sdk platform failed") - } - if pbs_req.Device.IP == "" { - t.Errorf("Parse device ip failed %s", pbs_req.Device.IP) - } -} - -func TestParseMalformedMobileRequest(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":0, - "buyeruid":"test_buyeruid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"5d748364ee9c46a2b112892fc3551b6f" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.1" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 1 { - t.Errorf("Parse ad units failed") - } - // We are expecting all user fields to be nil. Since no SDK version is passed in - if pbs_req.User.BuyerUID != "" { - t.Errorf("Parse user buyeruid failed %s", pbs_req.User.BuyerUID) - } - if pbs_req.User.Gender != "" { - t.Errorf("Parse user gender failed %s", pbs_req.User.Gender) - } - if pbs_req.User.Yob != 0 { - t.Errorf("Parse user year of birth failed %d", pbs_req.User.Yob) - } - if pbs_req.User.ID != "" { - t.Errorf("Parse user id failed %s", pbs_req.User.ID) - } - - if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { - t.Errorf("Parse app bundle failed") - } - if pbs_req.App.Ver != "0.0.1" { - t.Errorf("Parse app version failed") - } - - if pbs_req.Device.IFA != "test_device_ifa" { - t.Errorf("Parse device ifa failed") - } - if pbs_req.Device.OSV != "9.3.5" { - t.Errorf("Parse device osv failed") - } - if pbs_req.Device.OS != "iOS" { - t.Errorf("Parse device os failed") - } - if pbs_req.Device.Make != "Apple" { - t.Errorf("Parse device make failed") - } - if pbs_req.Device.Model != "iPhone6,1" { - t.Errorf("Parse device model failed") - } -} - -func TestParseRequestWithInstl(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":"F", - "buyeruid":"test_buyeruid", - "yob":2000, - "id":"testid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ], - "code":"5d748364ee9c46a2b112892fc3551b6f", - "instl": 1 - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.2" - }, - "sdk":{ - "version":"0.0.2", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if len(pbs_req.Bidders) != 2 { - t.Errorf("Should have 2 bidders. ") - } - if pbs_req.Bidders[0].AdUnits[0].Instl != 1 { - t.Errorf("Parse instl failed.") - } - if pbs_req.Bidders[1].AdUnits[0].Instl != 1 { - t.Errorf("Parse instl failed.") - } - -} - -func TestTimeouts(t *testing.T) { - doTimeoutTest(t, 10, 15, 10, 0) - doTimeoutTest(t, 10, 0, 10, 0) - doTimeoutTest(t, 5, 5, 10, 0) - doTimeoutTest(t, 15, 15, 0, 0) - doTimeoutTest(t, 15, 0, 20, 15) -} - -func doTimeoutTest(t *testing.T, expected int, requested int, max uint64, def uint64) { - t.Helper() - cfg := &config.AuctionTimeouts{ - Default: def, - Max: max, - } - body := fmt.Sprintf(`{ - "tid": "abcd", - "timeout_millis": %d, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.2" - }, - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bids": [ - { - "bidder": "ix" - } - ] - } - ] -}`, requested) - r := httptest.NewRequest("POST", "/auction", strings.NewReader(body)) - d, _ := dummycache.New() - parsed, err := ParsePBSRequest(r, cfg, d, &config.HostCookie{}) - if err != nil { - t.Fatalf("Unexpected err: %v", err) - } - if parsed.TimeoutMillis != int64(expected) { - t.Errorf("Expected %dms timeout, got %dms", expected, parsed.TimeoutMillis) - } -} - -func TestParsePBSRequestUsesHostCookie(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bidders": [ - { - "bidder": "bidder1", - "params": { - "id": "417", - "siteID": "test-site" - } - } - ] - } - ] - } - `) - r, err := http.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - if err != nil { - t.Fatalf("new request failed") - } - r.AddCookie(&http.Cookie{Name: "key", Value: "testcookie"}) - d, _ := dummycache.New() - hcc := config.HostCookie{ - CookieName: "key", - Family: "family", - OptOutCookie: config.Cookie{ - Name: "trp_optout", - Value: "true", - }, - } - - pbs_req, err2 := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err2 != nil { - t.Fatalf("Parse simple request failed %v", err2) - } - if uid, _, _ := pbs_req.Cookie.GetUID("family"); uid != "testcookie" { - t.Errorf("Failed to leverage host cookie space for user identifier") - } -} diff --git a/pbs/pbsresponse.go b/pbs/pbsresponse.go deleted file mode 100644 index b8cf2c19ff7..00000000000 --- a/pbs/pbsresponse.go +++ /dev/null @@ -1,84 +0,0 @@ -package pbs - -// PBSBid is a bid from the auction. These are produced by Adapters, and target a particular Ad Unit. -// -// This JSON format is a contract with both Prebid.js and Prebid-mobile. -// All changes *must* be backwards compatible, since clients cannot be forced to update their code. -type PBSBid struct { - // BidID identifies the Bid Request within the Ad Unit which this Bid targets. It should match one of - // the values inside PBSRequest.AdUnits[i].Bids[j].BidID. - BidID string `json:"bid_id"` - // AdUnitCode identifies the AdUnit which this Bid targets. - // It should match one of PBSRequest.AdUnits[i].Code, where "i" matches the AdUnit used in - // as BidID. - AdUnitCode string `json:"code"` - // Creative_id uniquely identifies the creative being served. It is not used by prebid-server, but - // it helps publishers and bidders identify and communicate about malicious or inappropriate ads. - // This project simply passes it along with the bid. - Creative_id string `json:"creative_id,omitempty"` - // CreativeMediaType shows whether the creative is a video or banner. - CreativeMediaType string `json:"media_type,omitempty"` - // BidderCode is the PBSBidder.BidderCode of the PBSBidder who made this bid. - BidderCode string `json:"bidder"` - // BidHash is the hash of the bidder's unique bid identifier for blockchain. It should not be sent to browser. - BidHash string `json:"-"` - // Price is the cpm, in US Dollars, which the bidder is willing to pay if this bid is chosen. - // TODO: Add support for other currencies someday. - Price float64 `json:"price"` - // NURL is a URL which returns ad markup, and should be called if the bid wins. - // If NURL and Adm are both defined, then Adm takes precedence. - NURL string `json:"nurl,omitempty"` - // Adm is the ad markup which should be used to deliver the ad, if this bid is chosen. - // If NURL and Adm are both defined, then Adm takes precedence. - Adm string `json:"adm,omitempty"` - // Width is the intended width which Adm should be shown, in pixels. - Width int64 `json:"width,omitempty"` - // Height is the intended width which Adm should be shown, in pixels. - Height int64 `json:"height,omitempty"` - // DealId is not used by prebid-server, but may be used by buyers and sellers who make special - // deals with each other. We simply pass this information along with the bid. - DealId string `json:"deal_id,omitempty"` - // CacheId is an ID in prebid-cache which can be used to fetch this ad's content. - // This supports prebid-mobile, which requires that the content be available from a URL. - CacheID string `json:"cache_id,omitempty"` - // Complete cache url returned from the prebid-cache. - // more flexible than a design that assumes the UUID is always appended to the end of the URL. - CacheURL string `json:"cache_url,omitempty"` - // ResponseTime is the number of milliseconds it took for the adapter to return a bid. - ResponseTime int `json:"response_time_ms,omitempty"` - AdServerTargeting map[string]string `json:"ad_server_targeting,omitempty"` -} - -// PBSBidSlice attaches the methods of sort.Interface to []PBSBid, ordering them by price. -// If two prices are equal, then the response time will be used as a tiebreaker. -// For more information, see https://golang.org/pkg/sort/#Interface -type PBSBidSlice []*PBSBid - -func (bids PBSBidSlice) Len() int { - return len(bids) -} - -func (bids PBSBidSlice) Less(i, j int) bool { - bidiResponseTimeInTerras := (float64(bids[i].ResponseTime) / 1000000000.0) - bidjResponseTimeInTerras := (float64(bids[j].ResponseTime) / 1000000000.0) - return bids[i].Price-bidiResponseTimeInTerras > bids[j].Price-bidjResponseTimeInTerras -} - -func (bids PBSBidSlice) Swap(i, j int) { - bids[i], bids[j] = bids[j], bids[i] -} - -type BidderDebug struct { - RequestURI string `json:"request_uri,omitempty"` - RequestBody string `json:"request_body,omitempty"` - ResponseBody string `json:"response_body,omitempty"` - StatusCode int `json:"status_code,omitempty"` -} - -type PBSResponse struct { - TID string `json:"tid,omitempty"` - Status string `json:"status,omitempty"` - BidderStatus []*PBSBidder `json:"bidder_status,omitempty"` - Bids PBSBidSlice `json:"bids,omitempty"` - BUrl string `json:"burl,omitempty"` -} diff --git a/pbs/pbsresponse_test.go b/pbs/pbsresponse_test.go deleted file mode 100644 index 0e51120cdf4..00000000000 --- a/pbs/pbsresponse_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package pbs - -import ( - "sort" - "testing" -) - -func TestSortBids(t *testing.T) { - bid1 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 0.0, - } - bid2 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 4.0, - } - bid3 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 2.0, - } - bid4 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 0.50, - } - - bids := make(PBSBidSlice, 0) - bids = append(bids, &bid1, &bid2, &bid3, &bid4) - - sort.Sort(bids) - if bids[0].Price != 4.0 { - t.Error("Expected 4.00 to be highest price") - } - if bids[1].Price != 2.0 { - t.Error("Expected 2.00 to be second highest price") - } - if bids[2].Price != 0.5 { - t.Error("Expected 0.50 to be third highest price") - } - if bids[3].Price != 0.0 { - t.Error("Expected 0.00 to be lowest price") - } -} - -func TestSortBidsWithResponseTimes(t *testing.T) { - bid1 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 1.0, - ResponseTime: 70, - } - bid2 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 1.0, - ResponseTime: 20, - } - bid3 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 1.0, - ResponseTime: 99, - } - - bids := make(PBSBidSlice, 0) - bids = append(bids, &bid1, &bid2, &bid3) - - sort.Sort(bids) - if bids[0] != &bid2 { - t.Error("Expected bid 2 to win") - } - if bids[1] != &bid1 { - t.Error("Expected bid 1 to be second") - } - if bids[2] != &bid3 { - t.Error("Expected bid 3 to be last") - } -} diff --git a/pbs/usersync.go b/pbs/usersync.go index 85b55f42aeb..d5043b8c13f 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -7,13 +7,10 @@ import ( "net/http" "net/url" "strings" - "time" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/server/ssl" "github.com/prebid/prebid-server/usersync" ) @@ -21,27 +18,10 @@ import ( // Recaptcha code from https://github.com/haisum/recaptcha/blob/master/recaptcha.go const RECAPTCHA_URL = "https://www.google.com/recaptcha/api/siteverify" -const ( - USERSYNC_OPT_OUT = "usersync.opt_outs" - USERSYNC_BAD_REQUEST = "usersync.bad_requests" - USERSYNC_SUCCESS = "usersync.%s.sets" -) - -// uidWithExpiry bundles the UID with an Expiration date. -// After the expiration, the UID is no longer valid. -type uidWithExpiry struct { - // UID is the ID given to a user by a particular bidder - UID string `json:"uid"` - // Expires is the time at which this UID should no longer apply. - Expires time.Time `json:"expires"` -} - type UserSyncDeps struct { ExternalUrl string RecaptchaSecret string HostCookieConfig *config.HostCookie - MetricsEngine metrics.MetricsEngine - PBSAnalytics analytics.PBSAnalyticsModule } // Struct for parsing json in google's response @@ -79,7 +59,7 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr rr := r.FormValue("g-recaptcha-response") if rr == "" { - http.Redirect(w, r, fmt.Sprintf("%s/static/optout.html", deps.ExternalUrl), 301) + http.Redirect(w, r, fmt.Sprintf("%s/static/optout.html", deps.ExternalUrl), http.StatusMovedPermanently) return } @@ -98,8 +78,8 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr pc.SetCookieOnResponse(w, false, deps.HostCookieConfig, deps.HostCookieConfig.TTLDuration()) if optout == "" { - http.Redirect(w, r, deps.HostCookieConfig.OptInURL, 301) + http.Redirect(w, r, deps.HostCookieConfig.OptInURL, http.StatusMovedPermanently) } else { - http.Redirect(w, r, deps.HostCookieConfig.OptOutURL, 301) + http.Redirect(w, r, deps.HostCookieConfig.OptOutURL, http.StatusMovedPermanently) } } diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 730d54b0acb..a24a139ea1d 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -120,7 +120,7 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s responseBody, err := ioutil.ReadAll(anResp.Body) if anResp.StatusCode != 200 { - logError(&errs, "Prebid Cache call to %s returned %d: %s", putURL, anResp.StatusCode, responseBody) + logError(&errs, "Prebid Cache call to %s returned %d: %s", c.putUrl, anResp.StatusCode, responseBody) return uuidsToReturn, errs } diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index 60237bbbb27..ec390364849 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -280,10 +280,18 @@ func assertStringEqual(t *testing.T, expected, actual string) { } } +type handlerResponseObject struct { + UUID string `json:"uuid"` +} + +type handlerResponse struct { + Responses []handlerResponseObject `json:"responses"` +} + func newHandler(numResponses int) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := response{ - Responses: make([]responseObject, numResponses), + resp := handlerResponse{ + Responses: make([]handlerResponseObject, numResponses), } for i := 0; i < numResponses; i++ { resp.Responses[i].UUID = strconv.Itoa(i) diff --git a/prebid_cache_client/prebid_cache.go b/prebid_cache_client/prebid_cache.go deleted file mode 100644 index cde7ec8d951..00000000000 --- a/prebid_cache_client/prebid_cache.go +++ /dev/null @@ -1,122 +0,0 @@ -package prebid_cache_client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - - "golang.org/x/net/context/ctxhttp" -) - -// This file is deprecated, and is only used to cache things for the legacy (/auction) endpoint. -// For /openrtb2/auction cache, see client.go in this package. - -type CacheObject struct { - Value interface{} - UUID string - IsVideo bool -} - -type BidCache struct { - Adm string `json:"adm,omitempty"` - NURL string `json:"nurl,omitempty"` - Width int64 `json:"width,omitempty"` - Height int64 `json:"height,omitempty"` -} - -// internal protocol objects -type putObject struct { - Type string `json:"type"` - Value interface{} `json:"value"` -} - -type putRequest struct { - Puts []putObject `json:"puts"` -} - -type responseObject struct { - UUID string `json:"uuid"` -} -type response struct { - Responses []responseObject `json:"responses"` -} - -var ( - client *http.Client - baseURL string - putURL string -) - -// InitPrebidCache setup the global prebid cache -func InitPrebidCache(baseurl string) { - baseURL = baseurl - putURL = fmt.Sprintf("%s/cache", baseURL) - - ts := &http.Transport{ - MaxIdleConns: 10, - IdleConnTimeout: 65, - } - - client = &http.Client{ - Transport: ts, - } -} - -// Put will send the array of objs and update each with a UUID -func Put(ctx context.Context, objs []*CacheObject) error { - // Fixes #197 - if len(objs) == 0 { - return nil - } - pr := putRequest{Puts: make([]putObject, len(objs))} - for i, obj := range objs { - if obj.IsVideo { - pr.Puts[i].Type = "xml" - } else { - pr.Puts[i].Type = "json" - } - pr.Puts[i].Value = obj.Value - } - // Don't want to escape the HTML for adm and nurl - buf := new(bytes.Buffer) - enc := json.NewEncoder(buf) - enc.SetEscapeHTML(false) - err := enc.Encode(pr) - if err != nil { - return err - } - - httpReq, err := http.NewRequest("POST", putURL, buf) - if err != nil { - return err - } - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - anResp, err := ctxhttp.Do(ctx, client, httpReq) - if err != nil { - return err - } - defer anResp.Body.Close() - - if anResp.StatusCode != 200 { - return fmt.Errorf("HTTP status code %d", anResp.StatusCode) - } - - var resp response - if err := json.NewDecoder(anResp.Body).Decode(&resp); err != nil { - return err - } - - if len(resp.Responses) != len(objs) { - return fmt.Errorf("Put response length didn't match") - } - - for i, r := range resp.Responses { - objs[i].UUID = r.UUID - } - - return nil -} diff --git a/prebid_cache_client/prebid_cache_test.go b/prebid_cache_client/prebid_cache_test.go deleted file mode 100644 index 65688789fd0..00000000000 --- a/prebid_cache_client/prebid_cache_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package prebid_cache_client - -import ( - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "fmt" -) - -var delay time.Duration -var ( - MaxValueLength = 1024 * 10 - MaxNumValues = 10 -) - -type putAnyObject struct { - Type string `json:"type"` - Value json.RawMessage `json:"value"` -} - -type putAnyRequest struct { - Puts []putAnyObject `json:"puts"` -} - -func DummyPrebidCacheServer(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read the request body.", http.StatusBadRequest) - return - } - defer r.Body.Close() - var put putAnyRequest - - err = json.Unmarshal(body, &put) - if err != nil { - http.Error(w, "Request body "+string(body)+" is not valid JSON.", http.StatusBadRequest) - return - } - - if len(put.Puts) > MaxNumValues { - http.Error(w, fmt.Sprintf("More keys than allowed: %d", MaxNumValues), http.StatusBadRequest) - return - } - - resp := response{ - Responses: make([]responseObject, len(put.Puts)), - } - for i, p := range put.Puts { - resp.Responses[i].UUID = fmt.Sprintf("UUID-%d", i+1) // deterministic for testing - if len(p.Value) > MaxValueLength { - http.Error(w, fmt.Sprintf("Value is larger than allowed size: %d", MaxValueLength), http.StatusBadRequest) - return - } - if len(p.Value) == 0 { - http.Error(w, "Missing value.", http.StatusBadRequest) - return - } - if p.Type != "xml" && p.Type != "json" { - http.Error(w, fmt.Sprintf("Type must be one of [\"json\", \"xml\"]. Found %v", p.Type), http.StatusBadRequest) - return - } - } - - bytes, err := json.Marshal(&resp) - if err != nil { - http.Error(w, "Failed to serialize UUIDs into JSON.", http.StatusInternalServerError) - return - } - if delay > 0 { - <-time.After(delay) - } - w.Header().Set("Content-Type", "application/json") - w.Write(bytes) -} - -func TestPrebidClient(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer)) - defer server.Close() - - cobj := make([]*CacheObject, 3) - - // example bids - cobj[0] = &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\"}", - NURL: "https://www.facebook.com/audiencenetwork/nurl/?partner=442648859414574&app=1995257847363113&placement=1997038003851764&auction=d3013e9e-ca55-4a86-9baa-d44e31355e1d&impression=bannerad1&request=7187783259538616534&bid=3832427901228167009&ortb_loss_code=0", - Width: 300, - Height: 250, - }, - } - cobj[1] = &CacheObject{ - IsVideo: false, - Value: &BidCache{ - Adm: "", - Width: 300, - Height: 250, - }, - } - cobj[2] = &CacheObject{ - IsVideo: true, - Value: "", - } - InitPrebidCache(server.URL) - - ctx := context.TODO() - err := Put(ctx, cobj) - if err != nil { - t.Fatalf("pbc put failed: %v", err) - } - - if cobj[0].UUID != "UUID-1" { - t.Errorf("First object UUID was '%s', should have been 'UUID-1'", cobj[0].UUID) - } - if cobj[1].UUID != "UUID-2" { - t.Errorf("Second object UUID was '%s', should have been 'UUID-2'", cobj[1].UUID) - } - if cobj[2].UUID != "UUID-3" { - t.Errorf("Third object UUID was '%s', should have been 'UUID-3'", cobj[2].UUID) - } - - delay = 5 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - err = Put(ctx, cobj) - if err == nil { - t.Fatalf("pbc put succeeded but should have timed out") - } -} - -// Prevents #197 -func TestEmptyBids(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Errorf("The server should not be called.") - }) - server := httptest.NewServer(handler) - defer server.Close() - - InitPrebidCache(server.URL) - - if err := Put(context.Background(), []*CacheObject{}); err != nil { - t.Errorf("Error on Put: %v", err) - } -} diff --git a/privacy/ccpa/parsedpolicy_test.go b/privacy/ccpa/parsedpolicy_test.go index 33563b50567..4bbb4cbfd0f 100644 --- a/privacy/ccpa/parsedpolicy_test.go +++ b/privacy/ccpa/parsedpolicy_test.go @@ -3,9 +3,7 @@ package ccpa import ( "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func TestValidateConsent(t *testing.T) { @@ -380,12 +378,3 @@ func TestShouldEnforce(t *testing.T) { assert.Equal(t, test.expected, result, test.description) } } - -type mockPolicWriter struct { - mock.Mock -} - -func (m *mockPolicWriter) Write(req *openrtb2.BidRequest) error { - args := m.Called(req) - return args.Error(0) -} diff --git a/router/router.go b/router/router.go index 90074753a5b..81623f13838 100644 --- a/router/router.go +++ b/router/router.go @@ -3,7 +3,6 @@ package router import ( "context" "crypto/tls" - "database/sql" "encoding/json" "fmt" "io/ioutil" @@ -12,34 +11,17 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints/events" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" - - "github.com/prebid/prebid-server/metrics" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adform" - "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/pubmatic" - "github.com/prebid/prebid-server/adapters/pulsepoint" - "github.com/prebid/prebid-server/adapters/rubicon" - "github.com/prebid/prebid-server/adapters/sovrn" analyticsConf "github.com/prebid/prebid-server/analytics/config" - "github.com/prebid/prebid-server/cache" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/cache/filecache" - "github.com/prebid/prebid-server/cache/postgrescache" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/endpoints" + "github.com/prebid/prebid-server/endpoints/events" infoEndpoints "github.com/prebid/prebid-server/endpoints/info" "github.com/prebid/prebid-server/endpoints/openrtb2" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" @@ -49,6 +31,8 @@ import ( storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/prebid-server/version" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -56,9 +40,6 @@ import ( "github.com/rs/cors" ) -var dataCache cache.Cache -var exchanges map[string]adapters.Adapter - // NewJsonDirectoryServer is used to serve .json files from a directory as a single blob. For example, // given a directory containing the files "a.json" and "b.json", this returns a Handle which serves JSON like: // @@ -123,51 +104,6 @@ func (m NoCache) ServeHTTP(w http.ResponseWriter, r *http.Request) { m.Handler.ServeHTTP(w, r) } -func loadDataCache(cfg *config.Configuration, db *sql.DB) (err error) { - switch cfg.DataCache.Type { - case "dummy": - dataCache, err = dummycache.New() - if err != nil { - glog.Fatalf("Dummy cache not configured: %s", err.Error()) - } - - case "postgres": - if db == nil { - return fmt.Errorf("Nil db cannot connect to postgres. Did you forget to set the config.stored_requests.postgres values?") - } - dataCache = postgrescache.New(db, postgrescache.CacheConfig{ - Size: cfg.DataCache.CacheSize, - TTL: cfg.DataCache.TTLSeconds, - }) - return nil - case "filecache": - dataCache, err = filecache.New(cfg.DataCache.Filename) - if err != nil { - return fmt.Errorf("FileCache Error: %s", err.Error()) - } - - default: - return fmt.Errorf("Unknown datacache.type: %s", cfg.DataCache.Type) - } - return nil -} - -func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter { - // These keys _must_ coincide with the bidder code in Prebid.js, if the adapter exists in both projects - return map[string]adapters.Adapter{ - "appnexus": appnexus.NewAppNexusLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), - "districtm": appnexus.NewAppNexusLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), - "ix": ix.NewIxLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint), - "pubmatic": pubmatic.NewPubmaticLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), - "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), - "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), - } -} - type Router struct { *httprouter.Router MetricsEngine *metricsConf.DetailedMetricsEngine @@ -211,10 +147,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R }, } - // Hack because of how legacy handles districtm - legacyBidderList := openrtb_ext.CoreBidderNames() - legacyBidderList = append(legacyBidderList, openrtb_ext.BidderName("districtm")) - p, _ := filepath.Abs(infoDirectory) bidderInfos, err := config.LoadBidderInfoFromDisk(p, cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) if err != nil { @@ -244,13 +176,10 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } // Metrics engine - r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList, syncerKeys) - db, shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) + r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.CoreBidderNames(), syncerKeys) + shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) // todo(zachbadgett): better shutdown r.Shutdown = shutdown - if err := loadDataCache(cfg, db); err != nil { - return nil, fmt.Errorf("Prebid Server could not load data cache: %v", err) - } pbsAnalytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics) @@ -270,7 +199,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R gvlVendorIDs := bidderInfos.ToGVLVendorIDMap() gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, gvlVendorIDs, generalHttpClient) - exchanges = newExchangeMap(cfg) cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, bidderInfos, r.MetricsEngine) @@ -301,10 +229,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, metrics.ReqTypeVideo) } - if cfg.EnableLegacyAuction { - r.POST("/auction", endpoints.Auction(cfg, syncersByBidder, gdprPerms, r.MetricsEngine, dataCache, exchanges)) - } - r.POST("/openrtb2/auction", openrtbEndpoint) r.POST("/openrtb2/video", videoEndpoint) r.GET("/openrtb2/amp", ampEndpoint) @@ -331,8 +255,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R HostCookieConfig: &(cfg.HostCookie), ExternalUrl: cfg.ExternalURL, RecaptchaSecret: cfg.RecaptchaSecret, - MetricsEngine: r.MetricsEngine, - PBSAnalytics: pbsAnalytics, } r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg.HostCookie, syncersByBidder, gdprPerms, pbsAnalytics, r.MetricsEngine)) diff --git a/router/router_test.go b/router/router_test.go index 24a7709c365..b4ceaff16a9 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "net/http" "net/http/httptest" - "os" "testing" "github.com/prebid/prebid-server/config" @@ -62,18 +61,6 @@ func TestNewJsonDirectoryServer(t *testing.T) { ensureHasKey(t, data, "aliastest") } -func TestExchangeMap(t *testing.T) { - exchanges := newExchangeMap(&config.Configuration{}) - bidderMap := openrtb_ext.BuildBidderMap() - for bidderName := range exchanges { - // OpenRTB doesn't support hardcoded aliases... so this test skips districtm, - // which was the only alias in the legacy adapter map. - if _, ok := bidderMap[bidderName]; bidderName != "districtm" && !ok { - t.Errorf("Bidder %s exists in exchange, but is not a part of the BidderMap.", bidderName) - } - } -} - func TestApplyBidderInfoConfigOverrides(t *testing.T) { var testCases = []struct { description string @@ -298,38 +285,6 @@ func TestNoCache(t *testing.T) { } } -func TestLoadDataCache(t *testing.T) { - // Test dummy - if err := loadDataCache(&config.Configuration{ - DataCache: config.DataCache{ - Type: "dummy", - }, - }, nil); err != nil { - t.Errorf("data cache: dummy: %s", err) - } - // Test postgres error - if err := loadDataCache(&config.Configuration{ - DataCache: config.DataCache{ - Type: "postgres", - }, - }, nil); err == nil { - t.Errorf("data cache: postgres: db nil should return error") - } - // Test file - d, _ := ioutil.TempDir("", "pbs-filecache") - defer os.RemoveAll(d) - f, _ := ioutil.TempFile(d, "file") - defer f.Close() - if err := loadDataCache(&config.Configuration{ - DataCache: config.DataCache{ - Type: "filecache", - Filename: f.Name(), - }, - }, nil); err != nil { - t.Errorf("data cache: filecache: %s", err) - } -} - var testDefReqConfig = config.DefReqConfig{ Type: "file", FileSystem: config.DefReqFiles{ diff --git a/server/prometheus.go b/server/prometheus.go index 4b9f7037d0a..6f0a0b2df45 100644 --- a/server/prometheus.go +++ b/server/prometheus.go @@ -9,13 +9,10 @@ import ( "github.com/prebid/prebid-server/config" metricsconfig "github.com/prebid/prebid-server/metrics/config" - prometheusMetrics "github.com/prebid/prebid-server/metrics/prometheus" ) func newPrometheusServer(cfg *config.Configuration, metrics *metricsconfig.DetailedMetricsEngine) *http.Server { - var proMetrics *prometheusMetrics.Metrics - - proMetrics = metrics.PrometheusMetrics + proMetrics := metrics.PrometheusMetrics if proMetrics == nil { glog.Fatal("Prometheus metrics configured, but a Prometheus metrics engine was not found. Cannot set up a Prometheus listener.") diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index a145a3b43a2..7d23f942d56 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -32,7 +32,7 @@ func TestAccountFetcher(t *testing.T) { assertErrorCount(t, 0, errs) assert.JSONEq(t, `{"disabled":false, "id":"valid"}`, string(account)) - account, errs = fetcher.FetchAccount(context.Background(), "nonexistent") + _, errs = fetcher.FetchAccount(context.Background(), "nonexistent") assertErrorCount(t, 1, errs) assert.Error(t, errs[0]) assert.Equal(t, stored_requests.NotFoundError{"nonexistent", "Account"}, errs[0]) diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index bc12caecb98..75a92e3f331 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -74,7 +74,6 @@ func NewFetcher(client *http.Client, endpoint string) *HttpFetcher { type HttpFetcher struct { client *http.Client Endpoint string - hasQuery bool Categories map[string]map[string]stored_requests.Category } diff --git a/stored_requests/backends/http_fetcher/fetcher_test.go b/stored_requests/backends/http_fetcher/fetcher_test.go index 30933181e1d..10d3984a818 100644 --- a/stored_requests/backends/http_fetcher/fetcher_test.go +++ b/stored_requests/backends/http_fetcher/fetcher_test.go @@ -1,10 +1,8 @@ package http_fetcher import ( - "bytes" "context" "encoding/json" - "io" "net/http" "net/http/httptest" "strings" @@ -168,42 +166,6 @@ func TestErrResponse(t *testing.T) { assert.Len(t, errs, 1) } -func assertSameContents(t *testing.T, expected map[string]json.RawMessage, actual map[string]json.RawMessage) { - if len(expected) != len(actual) { - t.Errorf("Wrong counts. Expected %d, actual %d", len(expected), len(actual)) - return - } - for expectedKey, expectedVal := range expected { - if actualVal, ok := actual[expectedKey]; ok { - if !bytes.Equal(expectedVal, actualVal) { - t.Errorf("actual[%s] value %s does not match expected: %s", expectedKey, string(actualVal), string(actualVal)) - } - } else { - t.Errorf("actual map missing expected key %s", expectedKey) - } - } -} - -func assertSameErrMsgs(t *testing.T, expected []string, actual []error) { - if len(expected) != len(actual) { - t.Errorf("Wrong error counts. Expected %d, actual %d", len(expected), len(actual)) - return - } - for i, expectedErr := range expected { - if actual[i].Error() != expectedErr { - t.Errorf("Wrong error[%d]. Expected %s, got %s", i, expectedErr, actual[i].Error()) - } - } -} - -type closeWrapper struct { - io.Reader -} - -func (w closeWrapper) Close() error { - return nil -} - func newFetcherBrokenBackend() (fetcher *HttpFetcher, closer func()) { handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) diff --git a/stored_requests/caches/nil_cache/nil_cache.go b/stored_requests/caches/nil_cache/nil_cache.go index d043ae55c96..88bbd404674 100644 --- a/stored_requests/caches/nil_cache/nil_cache.go +++ b/stored_requests/caches/nil_cache/nil_cache.go @@ -13,9 +13,7 @@ func (c *NilCache) Get(ctx context.Context, ids []string) map[string]json.RawMes } func (c *NilCache) Save(ctx context.Context, data map[string]json.RawMessage) { - return } func (c *NilCache) Invalidate(ctx context.Context, ids []string) { - return } diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index f682ff932f4..89022582ace 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -93,12 +93,12 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.Metr return } -// NewStoredRequests returns five things: +// NewStoredRequests returns: // -// 1. A DB connection, if one was created. This may be nil. -// 2. A function which should be called on shutdown for graceful cleanups. -// 3. A Fetcher which can be used to get Stored Requests for /openrtb2/auction -// 4. A Fetcher which can be used to get Stored Requests for /openrtb2/amp +// 1. A function which should be called on shutdown for graceful cleanups. +// 2. A Fetcher which can be used to get Stored Requests for /openrtb2/auction +// 3. A Fetcher which can be used to get Stored Requests for /openrtb2/amp +// 4. A Fetcher which can be used to get Account data // 5. A Fetcher which can be used to get Category Mapping data // 6. A Fetcher which can be used to get Stored Requests for /openrtb2/video // @@ -107,7 +107,7 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.Metr // // As a side-effect, it will add some endpoints to the router if the config calls for it. // In the future we should look for ways to simplify this so that it's not doing two things. -func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router) (db *sql.DB, shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { +func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router) (shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { // TODO: Switch this to be set in config defaults //if cfg.CategoryMapping.CacheEvents.Enabled && cfg.CategoryMapping.CacheEvents.Endpoint == "" { // cfg.CategoryMapping.CacheEvents.Endpoint = "/storedrequest/categorymapping" @@ -121,8 +121,6 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsE fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, &dbc) fetcher5, shutdown5 := CreateStoredRequests(&cfg.Accounts, metricsEngine, client, router, &dbc) - db = dbc.db - fetcher = fetcher1.(stored_requests.Fetcher) ampFetcher = fetcher2.(stored_requests.Fetcher) categoriesFetcher = fetcher3.(stored_requests.CategoryFetcher) diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go index 5b89943572f..7cb8f4b9b6d 100644 --- a/stored_requests/events/events.go +++ b/stored_requests/events/events.go @@ -77,7 +77,7 @@ func (e *EventListener) Listen(cache stored_requests.Cache, events EventProducer e.onInvalidate() } case <-e.stop: - break + return } } } diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index 4792db1969f..4e87db5dd0a 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -33,7 +33,7 @@ func TestEmptyOptOutCookie(t *testing.T) { func TestEmptyCookie(t *testing.T) { cookie := &Cookie{ - uids: make(map[string]uidWithExpiry, 0), + uids: make(map[string]uidWithExpiry), optOut: false, birthday: timestamp(), } From 08488d2c8b25b3effd7ed0e382dbc674536023f9 Mon Sep 17 00:00:00 2001 From: Mansi Nahar Date: Mon, 18 Oct 2021 13:34:22 -0400 Subject: [PATCH 114/140] Revert "Remove /auction Endpoint (#2033)" (#2048) This reverts commit fe211a9010bc264e43bfa7a396a3078c19de5ebf. --- adapters/adform/adform.go | 173 +++- adapters/adform/adform_test.go | 226 ++++- adapters/adhese/adhese.go | 2 +- adapters/adman/adman.go | 4 + adapters/appnexus/appnexus.go | 230 ++++- adapters/appnexus/appnexus_test.go | 421 ++++++++- adapters/connectad/connectad.go | 4 + adapters/consumable/instant.go | 2 +- adapters/consumable/utils.go | 20 + adapters/conversant/cnvr_legacy.go | 291 ++++++ adapters/conversant/cnvr_legacy_test.go | 853 ++++++++++++++++++ adapters/deepintent/deepintent.go | 4 + adapters/dmx/dmx_test.go | 4 + adapters/infoawarebidder_test.go | 1 + adapters/ix/ix.go | 248 ++++- adapters/ix/ix_test.go | 703 ++++++++++++++- adapters/legacy.go | 97 ++ adapters/openrtb_util.go | 174 ++++ adapters/openrtb_util_test.go | 543 +++++++++++ adapters/pubmatic/pubmatic.go | 315 ++++++- adapters/pubmatic/pubmatic_test.go | 673 +++++++++++++- adapters/pulsepoint/pulsepoint.go | 201 ++++- adapters/pulsepoint/pulsepoint_test.go | 294 +++++- adapters/rubicon/rubicon.go | 335 ++++++- adapters/rubicon/rubicon_test.go | 658 +++++++++++++- adapters/sharethrough/utils_test.go | 4 + adapters/sonobi/sonobi.go | 10 + adapters/sovrn/sovrn.go | 167 +++- adapters/sovrn/sovrn_test.go | 278 +++++- analytics/filesystem/file_module.go | 2 + analytics/pubstack/pubstack_module_test.go | 44 + cache/dummycache/dummycache.go | 65 ++ cache/dummycache/dummycache_test.go | 31 + cache/filecache/filecache.go | 123 +++ cache/filecache/filecache_test.go | 79 ++ cache/legacy.go | 33 + cache/postgrescache/postgrescache.go | 139 +++ cache/postgrescache/postgrescache_test.go | 94 ++ config/config.go | 17 + config/config_test.go | 13 + config/stored_requests.go | 1 + config/structlog.go | 4 +- endpoints/auction.go | 513 +++++++++++ endpoints/auction_test.go | 654 ++++++++++++++ endpoints/openrtb2/auction.go | 9 + endpoints/openrtb2/auction_test.go | 16 + endpoints/openrtb2/video_auction_test.go | 16 + exchange/auction.go | 7 + exchange/auction_test.go | 6 + exchange/bidder_test.go | 1 + exchange/exchange_test.go | 2 +- exchange/targeting_test.go | 19 +- go.mod | 3 + go.sum | 4 + main.go | 3 + metrics/config/metrics.go | 11 + metrics/config/metrics_test.go | 2 + metrics/go_metrics.go | 10 + metrics/go_metrics_test.go | 4 + metrics/metrics.go | 1 + metrics/metrics_mock.go | 5 + metrics/prometheus/prometheus.go | 4 + metrics/prometheus/prometheus_test.go | 22 + metrics/prometheus/type_conversion.go | 9 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_beachfront.go | 6 +- openrtb_ext/imp_nanointeractive.go | 8 +- pbs/pbsrequest.go | 403 +++++++++ pbs/pbsrequest_test.go | 735 +++++++++++++++ pbs/pbsresponse.go | 84 ++ pbs/pbsresponse_test.go | 88 ++ pbs/usersync.go | 26 +- prebid_cache_client/client.go | 2 +- prebid_cache_client/client_test.go | 12 +- prebid_cache_client/prebid_cache.go | 122 +++ prebid_cache_client/prebid_cache_test.go | 150 +++ privacy/ccpa/parsedpolicy_test.go | 11 + router/router.go | 94 +- router/router_test.go | 45 + server/prometheus.go | 5 +- .../backends/file_fetcher/fetcher_test.go | 2 +- .../backends/http_fetcher/fetcher.go | 1 + .../backends/http_fetcher/fetcher_test.go | 38 + stored_requests/caches/nil_cache/nil_cache.go | 2 + stored_requests/config/config.go | 14 +- stored_requests/events/events.go | 2 +- usersync/cookie_test.go | 2 +- 87 files changed, 10648 insertions(+), 107 deletions(-) create mode 100644 adapters/consumable/utils.go create mode 100644 adapters/conversant/cnvr_legacy.go create mode 100644 adapters/conversant/cnvr_legacy_test.go create mode 100644 adapters/legacy.go create mode 100644 adapters/openrtb_util.go create mode 100644 adapters/openrtb_util_test.go create mode 100644 cache/dummycache/dummycache.go create mode 100644 cache/dummycache/dummycache_test.go create mode 100644 cache/filecache/filecache.go create mode 100644 cache/filecache/filecache_test.go create mode 100644 cache/legacy.go create mode 100644 cache/postgrescache/postgrescache.go create mode 100644 cache/postgrescache/postgrescache_test.go create mode 100644 endpoints/auction.go create mode 100644 endpoints/auction_test.go create mode 100644 pbs/pbsrequest.go create mode 100644 pbs/pbsrequest_test.go create mode 100644 pbs/pbsresponse.go create mode 100644 pbs/pbsresponse_test.go create mode 100644 prebid_cache_client/prebid_cache.go create mode 100644 prebid_cache_client/prebid_cache_test.go diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index a432bb075b3..225c7af35d4 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -2,27 +2,31 @@ package adform import ( "bytes" + "context" "encoding/base64" "encoding/json" "errors" "fmt" + "io/ioutil" "net/http" "net/url" "strconv" "strings" + "github.com/buger/jsonparser" + "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" - - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/pbs" + "golang.org/x/net/context/ctxhttp" ) const version = "0.1.3" type AdformAdapter struct { + http *adapters.HTTPAdapter URL *url.URL version string } @@ -96,6 +100,155 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } +// used for cookies and such +func (a *AdformAdapter) Name() string { + return "adform" +} + +func (a *AdformAdapter) SkipNoCookies() bool { + return false +} + +func (a *AdformAdapter) Call(ctx context.Context, request *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + adformRequest, err := pbsRequestToAdformRequest(a, request, bidder) + if err != nil { + return nil, err + } + + uri := adformRequest.buildAdformUrl(a) + + debug := &pbs.BidderDebug{RequestURI: uri} + if request.IsDebug { + bidder.Debug = append(bidder.Debug, debug) + } + + httpRequest, err := http.NewRequest("GET", uri, nil) + if err != nil { + return nil, err + } + + httpRequest.Header = adformRequest.buildAdformHeaders(a) + + response, err := ctxhttp.Do(ctx, a.http.Client, httpRequest) + if err != nil { + return nil, err + } + + debug.StatusCode = response.StatusCode + + if response.StatusCode == 204 { + return nil, nil + } + + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + responseBody := string(body) + + if response.StatusCode == http.StatusBadRequest { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status %d; body: %s", response.StatusCode, responseBody), + } + } + + if response.StatusCode != 200 { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status %d; body: %s", response.StatusCode, responseBody), + } + } + + if request.IsDebug { + debug.ResponseBody = responseBody + } + + adformBids, err := parseAdformBids(body) + if err != nil { + return nil, err + } + + bids := toPBSBidSlice(adformBids, adformRequest) + + return bids, nil +} + +func pbsRequestToAdformRequest(a *AdformAdapter, request *pbs.PBSRequest, bidder *pbs.PBSBidder) (*adformRequest, error) { + adUnits := make([]*adformAdUnit, 0, len(bidder.AdUnits)) + for _, adUnit := range bidder.AdUnits { + var adformAdUnit adformAdUnit + if err := json.Unmarshal(adUnit.Params, &adformAdUnit); err != nil { + return nil, err + } + mid, err := adformAdUnit.MasterTagId.Int64() + if err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + if mid <= 0 { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("master tag(placement) id is invalid=%s", adformAdUnit.MasterTagId), + } + } + adformAdUnit.bidId = adUnit.BidID + adformAdUnit.adUnitCode = adUnit.Code + adUnits = append(adUnits, &adformAdUnit) + } + + userId, _, _ := request.Cookie.GetUID(a.Name()) + + gdprApplies := request.ParseGDPR() + if gdprApplies != "0" && gdprApplies != "1" { + gdprApplies = "" + } + consent := request.ParseConsent() + + return &adformRequest{ + adUnits: adUnits, + ip: request.Device.IP, + advertisingId: request.Device.IFA, + userAgent: request.Device.UA, + bidderCode: bidder.BidderCode, + isSecure: request.Secure == 1, + referer: request.Url, + userId: userId, + tid: request.Tid, + gdprApplies: gdprApplies, + consent: consent, + currency: defaultCurrency, + }, nil +} + +func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice { + bids := make(pbs.PBSBidSlice, 0) + + for i, bid := range adformBids { + adm, bidType := getAdAndType(bid) + if adm == "" { + continue + } + pbsBid := pbs.PBSBid{ + BidID: r.adUnits[i].bidId, + AdUnitCode: r.adUnits[i].adUnitCode, + BidderCode: r.bidderCode, + Price: bid.Price, + Adm: adm, + Width: int64(bid.Width), + Height: int64(bid.Height), + DealId: bid.DealId, + Creative_id: bid.CreativeId, + CreativeMediaType: string(bidType), + } + + bids = append(bids, &pbsBid) + } + + return bids +} + +// COMMON + func (r *adformRequest) buildAdformUrl(a *AdformAdapter) string { parameters := url.Values{} @@ -206,6 +359,20 @@ func parseAdformBids(response []byte) ([]*adformBid, error) { // BIDDER Interface +func NewAdformLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpointURL string) *AdformAdapter { + var uriObj *url.URL + uriObj, err := url.Parse(endpointURL) + if err != nil { + panic(fmt.Sprintf("Incorrect Adform request url %s, check the configuration, please.", endpointURL)) + } + + return &AdformAdapter{ + http: adapters.NewHTTPAdapter(httpConfig), + URL: uriObj, + version: version, + } +} + func (a *AdformAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { adformRequest, errors := openRtbToAdformRequest(request) if len(adformRequest.adUnits) == 0 { diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index 53a658f9715..53219f4c4c0 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -2,18 +2,26 @@ package adform import ( "bytes" + "context" "encoding/json" - "fmt" "net/http" + "net/http/httptest" "strconv" "testing" + "time" - "github.com/prebid/prebid-server/adapters" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters/adapterstest" + "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" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -63,6 +71,31 @@ type aBidInfo struct { buyerUID string secure bool currency string + delay time.Duration +} + +var adformTestData aBidInfo + +// Legacy auction tests + +func DummyAdformServer(w http.ResponseWriter, r *http.Request) { + errorString := assertAdformServerRequest(adformTestData, r, false) + if errorString != nil { + http.Error(w, *errorString, http.StatusInternalServerError) + return + } + + if adformTestData.delay > 0 { + <-time.After(adformTestData.delay) + } + + adformServerResponse, err := createAdformServerResponse(adformTestData) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(adformServerResponse) } func createAdformServerResponse(testData aBidInfo) ([]byte, error) { @@ -103,6 +136,168 @@ func createAdformServerResponse(testData aBidInfo) ([]byte, error) { return adformServerResponse, err } +func TestAdformBasicResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(DummyAdformServer)) + defer server.Close() + + adapter, ctx, prebidRequest := initTestData(server, t) + + bids, err := adapter.Call(ctx, prebidRequest, prebidRequest.Bidders[0]) + + if err != nil { + t.Fatalf("Should not have gotten adapter error: %v", err) + } + if len(bids) != 3 { + t.Fatalf("Received %d bids instead of 3", len(bids)) + } + expectedTypes := []openrtb_ext.BidType{ + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeBanner, + openrtb_ext.BidTypeVideo, + } + + for i, bid := range bids { + + if bid.CreativeMediaType != string(expectedTypes[i]) { + t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], bid.CreativeMediaType) + } + + matched := false + for _, tag := range adformTestData.tags { + if bid.AdUnitCode == tag.code { + matched = true + if bid.BidderCode != "adform" { + t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode) + } + if bid.Price != tag.price { + t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) + } + if bid.Width != int64(adformTestData.width) || bid.Height != int64(adformTestData.height) { + t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, adformTestData.width, adformTestData.height) + } + if bid.Adm != tag.content { + t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) + } + if bid.DealId != tag.dealId { + t.Errorf("Incorrect deal id '%s' expected '%s'", bid.DealId, tag.dealId) + } + if bid.Creative_id != tag.creativeId { + t.Errorf("Incorrect creative id '%s' expected '%s'", bid.Creative_id, tag.creativeId) + } + } + } + if !matched { + t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode) + } + } + + // same test but with request timing out + adformTestData.delay = 5 * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + bids, err = adapter.Call(ctx, prebidRequest, prebidRequest.Bidders[0]) + if err == nil { + t.Fatalf("Should have gotten a timeout error: %v", err) + } +} + +func initTestData(server *httptest.Server, t *testing.T) (*AdformAdapter, context.Context, *pbs.PBSRequest) { + adformTestData = createTestData(false) + + // prepare adapter + conf := *adapters.DefaultHTTPAdapterConfig + adapter := NewAdformLegacyAdapter(&conf, server.URL) + + prebidRequest := preparePrebidRequest(server.URL, t) + ctx := context.TODO() + + return adapter, ctx, prebidRequest +} + +func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { + body := preparePrebidRequestBody(adformTestData, t) + prebidHttpRequest := httptest.NewRequest("POST", serverUrl, body) + prebidHttpRequest.Header.Add("User-Agent", adformTestData.deviceUA) + prebidHttpRequest.Header.Add("Referer", adformTestData.referrer) + prebidHttpRequest.Header.Add("X-Real-IP", adformTestData.deviceIP) + + pbsCookie := usersync.ParseCookieFromRequest(prebidHttpRequest, &config.HostCookie{}) + pbsCookie.TrySync("adform", adformTestData.buyerUID) + fakeWriter := httptest.NewRecorder() + + pbsCookie.SetCookieOnResponse(fakeWriter, false, &config.HostCookie{Domain: ""}, time.Minute) + prebidHttpRequest.Header.Add("Cookie", fakeWriter.Header().Get("Set-Cookie")) + + cacheClient, _ := dummycache.New() + r, err := pbs.ParsePBSRequest(prebidHttpRequest, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, cacheClient, &config.HostCookie{}) + if err != nil { + t.Fatalf("ParsePBSRequest failed: %v", err) + } + if len(r.Bidders) != 1 { + t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(r.Bidders)) + } + if r.Bidders[0].BidderCode != "adform" { + t.Fatalf("ParsePBSRequest returned invalid bidder") + } + + // can't be set in preparePrebidRequestBody as will be lost during json serialization and deserialization + // for the adapters which don't support OpenRTB requests the old PBSRequest is created from OpenRTB request + // so User and Regs are copied from OpenRTB request, see legacy.go -> toLegacyRequest + regs := getRegs() + r.Regs = ®s + user := openrtb2.User{ + Ext: getUserExt(), + } + r.User = &user + + return r +} + +func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer { + prebidRequest := pbs.PBSRequest{ + AdUnits: make([]pbs.AdUnit, 4), + Device: &openrtb2.Device{ + UA: requestData.deviceUA, + IP: requestData.deviceIP, + IFA: requestData.deviceIFA, + }, + Tid: requestData.tid, + Secure: 0, + } + for i, tag := range requestData.tags { + prebidRequest.AdUnits[i] = pbs.AdUnit{ + Code: tag.code, + Sizes: []openrtb2.Format{ + { + W: int64(requestData.width), + H: int64(requestData.height), + }, + }, + Bids: []pbs.Bids{ + { + BidderCode: "adform", + BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), + Params: json.RawMessage(formatAdUnitJson(tag)), + }, + }, + } + } + + body := new(bytes.Buffer) + err := json.NewEncoder(body).Encode(prebidRequest) + if err != nil { + t.Fatalf("Json encoding failed: %v", err) + } + fmt.Println("body", body) + return body +} + +// OpenRTB auction tests + func TestOpenRTBRequest(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ Endpoint: "https://adx.adform.net"}) @@ -129,7 +324,7 @@ func TestOpenRTBRequest(t *testing.T) { } r.Header = httpRequests[0].Headers - errorString := assertAdformServerRequest(testData, r) + errorString := assertAdformServerRequest(testData, r, true) if errorString != nil { t.Errorf("Request error: %s", *errorString) } @@ -304,6 +499,16 @@ func TestOpenRTBSurpriseResponse(t *testing.T) { } } +// Properties tests + +func TestAdformProperties(t *testing.T) { + adapter := NewAdformLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "adx.adform.net/adx") + + if adapter.SkipNoCookies() != false { + t.Fatalf("should have been false") + } +} + // helpers func getRegs() openrtb2.Regs { @@ -383,7 +588,7 @@ func formatAdUnitParam(fieldName string, fieldValue string) string { return "" } -func assertAdformServerRequest(testData aBidInfo, r *http.Request) *string { +func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb bool) *string { if ok, err := equal("GET", r.Method, "HTTP method"); !ok { return err } @@ -393,8 +598,15 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request) *string { } } - midsWithCurrency := "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS" - queryString := "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency + var midsWithCurrency = "" + var queryString = "" + if isOpenRtb { + midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS" + queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency + } else { + midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9VVNEJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9VVNE&bWlkPTMyMzQ3JnJjdXI9VVNE" // no way to pass currency in legacy adapter + queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency + } if ok, err := equal(queryString, r.URL.RawQuery, "Query string"); !ok { return err diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index b23b49b3774..6fc12e3df5e 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -109,7 +109,7 @@ func (a *AdheseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap // Compose url endpointParams := macros.EndpointTemplateParams{AccountID: params.Account} - host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) + host, err := macros.ResolveMacros(*&a.endpointTemplate, endpointParams) if err != nil { errs = append(errs, WrapReqError("Could not compose url from template and request account val: "+err.Error())) return nil, errs diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index fd31bc9d14f..808951d3aba 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -25,6 +25,10 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } +type admanParams struct { + TagID string `json:"TagID"` +} + // MakeRequests create bid request for adman demand func (a *AdmanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index 3695f541532..b1004601774 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -1,6 +1,8 @@ package appnexus import ( + "bytes" + "context" "encoding/json" "errors" "fmt" @@ -13,6 +15,9 @@ import ( "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/pbs" + + "golang.org/x/net/context/ctxhttp" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" @@ -22,12 +27,22 @@ import ( const defaultPlatformID int = 5 -type adapter struct { +type AppNexusAdapter struct { + http *adapters.HTTPAdapter URI string iabCategoryMap map[string]string hbSource int } +// used for cookies and such +func (a *AppNexusAdapter) Name() string { + return "adnxs" +} + +func (a *AppNexusAdapter) SkipNoCookies() bool { + return false +} + type KeyVal struct { Key string `json:"key,omitempty"` Values []string `json:"value,omitempty"` @@ -37,6 +52,21 @@ type appnexusAdapterOptions struct { IabCategories map[string]string `json:"iab_categories"` } +type appnexusParams struct { + LegacyPlacementId int `json:"placementId"` + LegacyInvCode string `json:"invCode"` + LegacyTrafficSourceCode string `json:"trafficSourceCode"` + PlacementId int `json:"placement_id"` + InvCode string `json:"inv_code"` + Member string `json:"member"` + Keywords []KeyVal `json:"keywords"` + TrafficSourceCode string `json:"traffic_source_code"` + Reserve float64 `json:"reserve"` + Position string `json:"position"` + UsePmtRule *bool `json:"use_pmt_rule"` + PrivateSizes json.RawMessage `json:"private_sizes"` +} + type appnexusImpExtAppnexus struct { PlacementID int `json:"placement_id,omitempty"` Keywords string `json:"keywords,omitempty"` @@ -85,7 +115,181 @@ type appnexusReqExt struct { var maxImpsPerReq = 10 -func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + anReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), supportedMediaTypes) + + if err != nil { + return nil, err + } + uri := a.URI + for i, unit := range bidder.AdUnits { + var params appnexusParams + err := json.Unmarshal(unit.Params, ¶ms) + if err != nil { + return nil, err + } + // Accept legacy Appnexus parameters if we don't have modern ones + // Don't worry if both is set as validation rules should prevent, and this is temporary anyway. + if params.PlacementId == 0 && params.LegacyPlacementId != 0 { + params.PlacementId = params.LegacyPlacementId + } + if params.InvCode == "" && params.LegacyInvCode != "" { + params.InvCode = params.LegacyInvCode + } + if params.TrafficSourceCode == "" && params.LegacyTrafficSourceCode != "" { + params.TrafficSourceCode = params.LegacyTrafficSourceCode + } + + if params.PlacementId == 0 && (params.InvCode == "" || params.Member == "") { + return nil, &errortypes.BadInput{ + Message: "No placement or member+invcode provided", + } + } + + // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply + if len(anReq.Imp) <= i { + break + } + if params.InvCode != "" { + anReq.Imp[i].TagID = params.InvCode + if params.Member != "" { + // this assumes that the same member ID is used across all tags, which should be the case + uri = appendMemberId(a.URI, params.Member) + } + + } + if params.Reserve > 0 { + anReq.Imp[i].BidFloor = params.Reserve // TODO: we need to factor in currency here if non-USD + } + if anReq.Imp[i].Banner != nil && params.Position != "" { + if params.Position == "above" { + anReq.Imp[i].Banner.Pos = openrtb2.AdPositionAboveTheFold.Ptr() + } else if params.Position == "below" { + anReq.Imp[i].Banner.Pos = openrtb2.AdPositionBelowTheFold.Ptr() + } + } + + kvs := make([]string, 0, len(params.Keywords)*2) + for _, kv := range params.Keywords { + if len(kv.Values) == 0 { + kvs = append(kvs, kv.Key) + } else { + for _, val := range kv.Values { + kvs = append(kvs, fmt.Sprintf("%s=%s", kv.Key, val)) + } + + } + } + + keywordStr := strings.Join(kvs, ",") + + impExt := appnexusImpExt{Appnexus: appnexusImpExtAppnexus{ + PlacementID: params.PlacementId, + TrafficSourceCode: params.TrafficSourceCode, + Keywords: keywordStr, + UsePmtRule: params.UsePmtRule, + PrivateSizes: params.PrivateSizes, + }} + anReq.Imp[i].Ext, err = json.Marshal(&impExt) + } + + reqJSON, err := json.Marshal(anReq) + if err != nil { + return nil, err + } + + debug := &pbs.BidderDebug{ + RequestURI: uri, + } + + if req.IsDebug { + debug.RequestBody = string(reqJSON) + bidder.Debug = append(bidder.Debug, debug) + } + + httpReq, err := http.NewRequest("POST", uri, bytes.NewBuffer(reqJSON)) + httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") + httpReq.Header.Add("Accept", "application/json") + + anResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) + if err != nil { + return nil, err + } + + debug.StatusCode = anResp.StatusCode + + if anResp.StatusCode == 204 { + return nil, nil + } + + defer anResp.Body.Close() + body, err := ioutil.ReadAll(anResp.Body) + if err != nil { + return nil, err + } + responseBody := string(body) + + if anResp.StatusCode == http.StatusBadRequest { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, responseBody), + } + } + + if anResp.StatusCode != http.StatusOK { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, responseBody), + } + } + + if req.IsDebug { + debug.ResponseBody = responseBody + } + + var bidResp openrtb2.BidResponse + err = json.Unmarshal(body, &bidResp) + if err != nil { + return nil, err + } + + bids := make(pbs.PBSBidSlice, 0) + + for _, sb := range bidResp.SeatBid { + for _, bid := range sb.Bid { + bidID := bidder.LookupBidID(bid.ImpID) + if bidID == "" { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), + } + } + + pbid := pbs.PBSBid{ + BidID: bidID, + AdUnitCode: bid.ImpID, + BidderCode: bidder.BidderCode, + Price: bid.Price, + Adm: bid.AdM, + Creative_id: bid.CrID, + Width: bid.W, + Height: bid.H, + DealId: bid.DealID, + NURL: bid.NURL, + } + + var impExt appnexusBidExt + if err := json.Unmarshal(bid.Ext, &impExt); err == nil { + if mediaType, err := getMediaTypeForBid(&impExt); err == nil { + pbid.CreativeMediaType = string(mediaType) + bids = append(bids, &pbid) + } + } + } + } + + return bids, nil +} + +func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { memberIds := make(map[string]bool) errs := make([]error, 0, len(request.Imp)) @@ -179,7 +383,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E requests := make([]*adapters.RequestData, 0, len(podImps)) for _, podImps := range podImps { - reqExt.Appnexus.AdPodId = generatePodID() + reqExt.Appnexus.AdPodId = generatePodId() reqs, errors := splitRequests(podImps, request, reqExt, thisURI, errs) requests = append(requests, reqs...) @@ -191,7 +395,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E return splitRequests(imps, request, reqExt, thisURI, errs) } -func generatePodID() string { +func generatePodId() string { val := rand.Int63() return fmt.Sprint(val) } @@ -357,7 +561,7 @@ func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string { return strings.Join(kvs, ",") } -func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -392,7 +596,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest bid.Cat = []string{iabCategory} } else if len(bid.Cat) > 1 { //create empty categories array to force bid to be rejected - bid.Cat = make([]string, 0) + bid.Cat = make([]string, 0, 0) } impVideo := &openrtb_ext.ExtBidPrebidVideo{ @@ -434,7 +638,7 @@ func getMediaTypeForBid(bid *appnexusBidExt) (openrtb_ext.BidType, error) { } // getIabCategoryForBid maps an appnexus brand id to an IAB category. -func (a *adapter) getIabCategoryForBid(bid *appnexusBidExt) (string, error) { +func (a *AppNexusAdapter) getIabCategoryForBid(bid *appnexusBidExt) (string, error) { brandIDString := strconv.Itoa(bid.Appnexus.BrandCategory) if iabCategory, ok := a.iabCategoryMap[brandIDString]; ok { return iabCategory, nil @@ -453,7 +657,7 @@ func appendMemberId(uri string, memberId string) string { // Builder builds a new instance of the AppNexus adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - bidder := &adapter{ + bidder := &AppNexusAdapter{ URI: config.Endpoint, iabCategoryMap: loadCategoryMapFromFileSystem(), hbSource: resolvePlatformID(config.PlatformID), @@ -461,6 +665,16 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } +// NewAppNexusLegacyAdapter builds a legacy version of the AppNexus adapter. +func NewAppNexusLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpoint, platformID string) *AppNexusAdapter { + return &AppNexusAdapter{ + http: adapters.NewHTTPAdapter(httpConfig), + URI: endpoint, + iabCategoryMap: loadCategoryMapFromFileSystem(), + hbSource: resolvePlatformID(platformID), + } +} + func resolvePlatformID(platformID string) int { if len(platformID) > 0 { if val, err := strconv.Atoi(platformID); err == nil { diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index 6399be1a5bb..cad52348134 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -1,17 +1,29 @@ package appnexus import ( + "bytes" + "context" "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" "regexp" "testing" + "time" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" + + "fmt" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { @@ -46,7 +58,7 @@ func TestMemberQueryParam(t *testing.T) { } func TestVideoSinglePod(t *testing.T) { - var a adapter + var a AppNexusAdapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -84,7 +96,7 @@ func TestVideoSinglePod(t *testing.T) { } func TestVideoSinglePodManyImps(t *testing.T) { - var a adapter + var a AppNexusAdapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -142,7 +154,7 @@ func TestVideoSinglePodManyImps(t *testing.T) { } func TestVideoTwoPods(t *testing.T) { - var a adapter + var a AppNexusAdapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -194,7 +206,7 @@ func TestVideoTwoPods(t *testing.T) { } func TestVideoTwoPodsManyImps(t *testing.T) { - var a adapter + var a AppNexusAdapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -269,3 +281,396 @@ func TestVideoTwoPodsManyImps(t *testing.T) { assert.Len(t, podIds, 2, "Incorrect number of unique pod ids") } + +// ---------------------------------------------------------------------------- +// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we +// clean up the existing code and make everything openrtb2. + +type anTagInfo struct { + code string + invCode string + placementID int + trafficSourceCode string + in_keywords string + out_keywords string + reserve float64 + position string + bid float64 + content string + mediaType string +} + +type anBidInfo struct { + memberID string + domain string + page string + accountID int + siteID int + tags []anTagInfo + deviceIP string + deviceUA string + buyerUID string + delay time.Duration +} + +var andata anBidInfo + +func DummyAppNexusServer(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 + } + + memberID := r.FormValue("member_id") + if memberID != andata.memberID { + http.Error(w, fmt.Sprintf("Member ID '%s' doesn't match '%s", memberID, andata.memberID), http.StatusInternalServerError) + return + } + + resp := openrtb2.BidResponse{ + ID: breq.ID, + BidID: "a-random-id", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "Buyer Member ID", + Bid: make([]openrtb2.Bid, 0, 2), + }, + }, + } + + for i, imp := range breq.Imp { + var aix appnexusImpExt + err = json.Unmarshal(imp.Ext, &aix) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Either placementID or member+invCode must be specified + has_placement := false + if aix.Appnexus.PlacementID != 0 { + if aix.Appnexus.PlacementID != andata.tags[i].placementID { + http.Error(w, fmt.Sprintf("Placement ID '%d' doesn't match '%d", aix.Appnexus.PlacementID, + andata.tags[i].placementID), http.StatusInternalServerError) + return + } + has_placement = true + } + if memberID != "" && imp.TagID != "" { + if imp.TagID != andata.tags[i].invCode { + http.Error(w, fmt.Sprintf("Inv Code '%s' doesn't match '%s", imp.TagID, + andata.tags[i].invCode), http.StatusInternalServerError) + return + } + has_placement = true + } + if !has_placement { + http.Error(w, fmt.Sprintf("Either placement or member+inv not present"), http.StatusInternalServerError) + return + } + + if aix.Appnexus.Keywords != andata.tags[i].out_keywords { + http.Error(w, fmt.Sprintf("Keywords '%s' doesn't match '%s", aix.Appnexus.Keywords, + andata.tags[i].out_keywords), http.StatusInternalServerError) + return + } + + if aix.Appnexus.TrafficSourceCode != andata.tags[i].trafficSourceCode { + http.Error(w, fmt.Sprintf("Traffic source code '%s' doesn't match '%s", aix.Appnexus.TrafficSourceCode, + andata.tags[i].trafficSourceCode), http.StatusInternalServerError) + return + } + if imp.BidFloor != andata.tags[i].reserve { + http.Error(w, fmt.Sprintf("Bid floor '%.2f' doesn't match '%.2f", imp.BidFloor, + andata.tags[i].reserve), http.StatusInternalServerError) + return + } + if imp.Banner == nil && imp.Video == nil { + http.Error(w, fmt.Sprintf("No banner or app object sent"), http.StatusInternalServerError) + return + } + if (imp.Banner == nil && andata.tags[i].mediaType == "banner") || (imp.Banner != nil && andata.tags[i].mediaType != "banner") { + http.Error(w, fmt.Sprintf("Invalid impression type - banner"), http.StatusInternalServerError) + return + } + if (imp.Video == nil && andata.tags[i].mediaType == "video") || (imp.Video != nil && andata.tags[i].mediaType != "video") { + http.Error(w, fmt.Sprintf("Invalid impression type - video"), http.StatusInternalServerError) + return + } + + if imp.Banner != nil { + if len(imp.Banner.Format) == 0 { + http.Error(w, fmt.Sprintf("Empty imp.banner.format array"), http.StatusInternalServerError) + return + } + if andata.tags[i].position == "above" && *imp.Banner.Pos != openrtb2.AdPosition(1) { + http.Error(w, fmt.Sprintf("Mismatch in position - expected 1 for atf"), http.StatusInternalServerError) + return + } + if andata.tags[i].position == "below" && *imp.Banner.Pos != openrtb2.AdPosition(3) { + http.Error(w, fmt.Sprintf("Mismatch in position - expected 3 for btf"), http.StatusInternalServerError) + return + } + } + if imp.Video != nil { + // TODO: add more validations + if len(imp.Video.MIMEs) == 0 { + http.Error(w, fmt.Sprintf("Empty imp.video.mimes array"), http.StatusInternalServerError) + return + } + if len(imp.Video.Protocols) == 0 { + http.Error(w, fmt.Sprintf("Empty imp.video.protocols array"), http.StatusInternalServerError) + return + } + for _, protocol := range imp.Video.Protocols { + if protocol < 1 || protocol > 8 { + http.Error(w, fmt.Sprintf("Invalid video protocol %d", protocol), http.StatusInternalServerError) + return + } + } + } + + resBid := openrtb2.Bid{ + ID: "random-id", + ImpID: imp.ID, + Price: andata.tags[i].bid, + AdM: andata.tags[i].content, + Ext: json.RawMessage(fmt.Sprintf(`{"appnexus":{"bid_ad_type":%d}}`, bidTypeToInt(andata.tags[i].mediaType))), + } + + if imp.Video != nil { + resBid.Attr = []openrtb2.CreativeAttribute{openrtb2.CreativeAttribute(6)} + } + resp.SeatBid[0].Bid = append(resp.SeatBid[0].Bid, resBid) + } + + // TODO: are all of these valid for app? + if breq.Site == nil { + http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) + return + } + if breq.Site.Domain != andata.domain { + http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, andata.domain), http.StatusInternalServerError) + return + } + if breq.Site.Page != andata.page { + http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, andata.page), http.StatusInternalServerError) + return + } + if breq.Device.UA != andata.deviceUA { + http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s", breq.Device.UA, andata.deviceUA), http.StatusInternalServerError) + return + } + if breq.Device.IP != andata.deviceIP { + http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s", breq.Device.IP, andata.deviceIP), http.StatusInternalServerError) + return + } + if breq.User.BuyerUID != andata.buyerUID { + http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s", breq.User.BuyerUID, andata.buyerUID), http.StatusInternalServerError) + return + } + + if andata.delay > 0 { + <-time.After(andata.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 bidTypeToInt(bidType string) int { + switch bidType { + case "banner": + return 0 + case "video": + return 1 + case "audio": + return 2 + case "native": + return 3 + default: + return -1 + } +} +func TestAppNexusLegacyBasicResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(DummyAppNexusServer)) + defer server.Close() + + andata = anBidInfo{ + domain: "nytimes.com", + page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", + tags: make([]anTagInfo, 2), + deviceIP: "25.91.96.36", + deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", + buyerUID: "23482348223", + memberID: "958", + } + andata.tags[0] = anTagInfo{ + code: "first-tag", + placementID: 8394, + bid: 1.67, + trafficSourceCode: "ppc-exchange", + content: "huh", + in_keywords: "[{ \"key\": \"genre\", \"value\": [\"jazz\", \"pop\"] }, {\"key\": \"myEmptyKey\", \"value\": []}]", + out_keywords: "genre=jazz,genre=pop,myEmptyKey", + reserve: 1.50, + position: "below", + mediaType: "banner", + } + andata.tags[1] = anTagInfo{ + code: "second-tag", + invCode: "leftbottom", + bid: 3.22, + trafficSourceCode: "taboola", + content: "yow!", + in_keywords: "[{ \"key\": \"genre\", \"value\": [\"rock\", \"pop\"] }, {\"key\": \"myKey\", \"value\": [\"myVal\"]}]", + out_keywords: "genre=rock,genre=pop,myKey=myVal", + reserve: 0.75, + position: "above", + mediaType: "video", + } + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewAppNexusLegacyAdapter(&conf, server.URL, "") + + pbin := pbs.PBSRequest{ + AdUnits: make([]pbs.AdUnit, 2), + } + for i, tag := range andata.tags { + var params json.RawMessage + if tag.placementID > 0 { + params = json.RawMessage(fmt.Sprintf("{\"placementId\": %d, \"member\": \"%s\", \"keywords\": %s, "+ + "\"trafficSourceCode\": \"%s\", \"reserve\": %.3f, \"position\": \"%s\"}", + tag.placementID, andata.memberID, tag.in_keywords, tag.trafficSourceCode, tag.reserve, tag.position)) + } else { + params = json.RawMessage(fmt.Sprintf("{\"invCode\": \"%s\", \"member\": \"%s\", \"keywords\": %s, "+ + "\"trafficSourceCode\": \"%s\", \"reserve\": %.3f, \"position\": \"%s\"}", + tag.invCode, andata.memberID, tag.in_keywords, tag.trafficSourceCode, tag.reserve, tag.position)) + } + + pbin.AdUnits[i] = pbs.AdUnit{ + Code: tag.code, + MediaTypes: []string{tag.mediaType}, + Sizes: []openrtb2.Format{ + { + W: 300, + H: 600, + }, + { + W: 300, + H: 250, + }, + }, + Bids: []pbs.Bids{ + { + BidderCode: "appnexus", + BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), + Params: params, + }, + }, + } + if tag.mediaType == "video" { + pbin.AdUnits[i].Video = pbs.PBSVideo{ + Mimes: []string{"video/mp4"}, + Minduration: 15, + Maxduration: 30, + Startdelay: 5, + Skippable: 0, + PlaybackMethod: 1, + Protocols: []int8{2, 3}, + } + } + } + + body := new(bytes.Buffer) + err := json.NewEncoder(body).Encode(pbin) + if err != nil { + t.Fatalf("Json encoding failed: %v", err) + } + + req := httptest.NewRequest("POST", server.URL, body) + req.Header.Add("Referer", andata.page) + req.Header.Add("User-Agent", andata.deviceUA) + req.Header.Add("X-Real-IP", andata.deviceIP) + + pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) + pc.TrySync("adnxs", andata.buyerUID) + 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 != "appnexus" { + 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 andata.tags { + if bid.AdUnitCode == tag.code { + matched = true + if bid.CreativeMediaType != tag.mediaType { + t.Errorf("Incorrect Creative MediaType '%s'. Expected '%s'", bid.CreativeMediaType, tag.mediaType) + } + if bid.BidderCode != "appnexus" { + 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.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 + andata.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/connectad/connectad.go b/adapters/connectad/connectad.go index 5c30e3a6adc..9827ebcea7b 100644 --- a/adapters/connectad/connectad.go +++ b/adapters/connectad/connectad.go @@ -18,6 +18,10 @@ type ConnectAdAdapter struct { endpoint string } +type connectadImpExt struct { + ConnectAd openrtb_ext.ExtImpConnectAd `json:"connectad"` +} + // Builder builds a new instance of the ConnectAd adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &ConnectAdAdapter{ diff --git a/adapters/consumable/instant.go b/adapters/consumable/instant.go index a6162d44e22..5a32fef8837 100644 --- a/adapters/consumable/instant.go +++ b/adapters/consumable/instant.go @@ -9,7 +9,7 @@ type instant interface { // Send a real instance when you construct it in adapter_map.go type realInstant struct{} -func (realInstant) Now() time.Time { +func (_ realInstant) Now() time.Time { return time.Now() } diff --git a/adapters/consumable/utils.go b/adapters/consumable/utils.go new file mode 100644 index 00000000000..64e4872c619 --- /dev/null +++ b/adapters/consumable/utils.go @@ -0,0 +1,20 @@ +package consumable + +import ( + netUrl "net/url" +) + +/** + * Creates a snippet of HTML that retrieves the specified `url` + * Returns HTML snippet that contains the img src = set to `url` + */ +func createTrackPixelHtml(url *string) string { + if url == nil { + return "" + } + + escapedUrl := netUrl.QueryEscape(*url) + img := "
" + + "
" + return img +} diff --git a/adapters/conversant/cnvr_legacy.go b/adapters/conversant/cnvr_legacy.go new file mode 100644 index 00000000000..eff1afc5d32 --- /dev/null +++ b/adapters/conversant/cnvr_legacy.go @@ -0,0 +1,291 @@ +package conversant + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "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 ConversantLegacyAdapter struct { + http *adapters.HTTPAdapter + URI string +} + +// Corresponds to the bidder name in cookies and requests +func (a *ConversantLegacyAdapter) Name() string { + return "conversant" +} + +// Return true so no request will be sent unless user has been sync'ed. +func (a *ConversantLegacyAdapter) SkipNoCookies() bool { + return true +} + +type conversantParams struct { + SiteID string `json:"site_id"` + Secure *int8 `json:"secure"` + TagID string `json:"tag_id"` + Position *int8 `json:"position"` + BidFloor float64 `json:"bidfloor"` + Mobile *int8 `json:"mobile"` + MIMEs []string `json:"mimes"` + API []int8 `json:"api"` + Protocols []int8 `json:"protocols"` + MaxDuration *int64 `json:"maxduration"` +} + +func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + cnvrReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) + + if err != nil { + return nil, err + } + + // Create a map of impression objects for both request creation + // and response parsing. + + impMap := make(map[string]*openrtb2.Imp, len(cnvrReq.Imp)) + for idx := range cnvrReq.Imp { + impMap[cnvrReq.Imp[idx].ID] = &cnvrReq.Imp[idx] + } + + // Fill in additional info from custom params + + for _, unit := range bidder.AdUnits { + var params conversantParams + + imp := impMap[unit.Code] + if imp == nil { + // Skip ad units that do not have corresponding impressions. + continue + } + + err := json.Unmarshal(unit.Params, ¶ms) + if err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + + // Fill in additional Site info + if params.SiteID != "" { + if cnvrReq.Site != nil { + cnvrReq.Site.ID = params.SiteID + } + if cnvrReq.App != nil { + cnvrReq.App.ID = params.SiteID + } + } + + if params.Mobile != nil && !(cnvrReq.Site == nil) { + cnvrReq.Site.Mobile = *params.Mobile + } + + // Fill in additional impression info + + imp.DisplayManager = "prebid-s2s" + imp.DisplayManagerVer = "1.0.1" + imp.BidFloor = params.BidFloor + imp.TagID = params.TagID + + var position *openrtb2.AdPosition + if params.Position != nil { + position = openrtb2.AdPosition(*params.Position).Ptr() + } + + if imp.Banner != nil { + imp.Banner.Pos = position + } else if imp.Video != nil { + imp.Video.Pos = position + + if len(params.API) > 0 { + imp.Video.API = make([]openrtb2.APIFramework, 0, len(params.API)) + for _, api := range params.API { + imp.Video.API = append(imp.Video.API, openrtb2.APIFramework(api)) + } + } + + // Include protocols, mimes, and max duration if specified + // These properties can also be specified in ad unit's video object, + // but are overridden if the custom params object also contains them. + + if len(params.Protocols) > 0 { + imp.Video.Protocols = make([]openrtb2.Protocol, 0, len(params.Protocols)) + for _, protocol := range params.Protocols { + imp.Video.Protocols = append(imp.Video.Protocols, openrtb2.Protocol(protocol)) + } + } + + if len(params.MIMEs) > 0 { + imp.Video.MIMEs = make([]string, len(params.MIMEs)) + copy(imp.Video.MIMEs, params.MIMEs) + } + + if params.MaxDuration != nil { + imp.Video.MaxDuration = *params.MaxDuration + } + } + + // Take care not to override the global secure flag + + if (imp.Secure == nil || *imp.Secure == 0) && params.Secure != nil { + imp.Secure = params.Secure + } + } + + // Do a quick check on required parameters + + if cnvrReq.Site != nil && cnvrReq.Site.ID == "" { + return nil, &errortypes.BadInput{ + Message: "Missing site id", + } + } + + if cnvrReq.App != nil && cnvrReq.App.ID == "" { + return nil, &errortypes.BadInput{ + Message: "Missing app id", + } + } + + // Start capturing debug info + + debug := &pbs.BidderDebug{ + RequestURI: a.URI, + } + + if cnvrReq.Device == nil { + cnvrReq.Device = &openrtb2.Device{} + } + + // Convert request to json to be sent over http + + j, _ := json.Marshal(cnvrReq) + + if req.IsDebug { + debug.RequestBody = string(j) + bidder.Debug = append(bidder.Debug, debug) + } + + httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(j)) + httpReq.Header.Add("Content-Type", "application/json") + httpReq.Header.Add("Accept", "application/json") + + resp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if req.IsDebug { + debug.StatusCode = resp.StatusCode + } + + if resp.StatusCode == 204 { + return nil, nil + } + + body, err := ioutil.ReadAll(resp.Body) + + if err != nil { + return nil, err + } + + if resp.StatusCode == http.StatusBadRequest { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), + } + } + + if resp.StatusCode != 200 { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), + } + } + + if req.IsDebug { + debug.ResponseBody = string(body) + } + + var bidResp openrtb2.BidResponse + + err = json.Unmarshal(body, &bidResp) + if err != nil { + return nil, &errortypes.BadServerResponse{ + Message: err.Error(), + } + } + + bids := make(pbs.PBSBidSlice, 0) + + for _, seatbid := range bidResp.SeatBid { + for _, bid := range seatbid.Bid { + if bid.Price <= 0 { + continue + } + + imp := impMap[bid.ImpID] + if imp == nil { + // All returned bids should have a matching impression + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown impression id '%s'", bid.ImpID), + } + } + + bidID := bidder.LookupBidID(bid.ImpID) + if bidID == "" { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), + } + } + + pbsBid := pbs.PBSBid{ + BidID: bidID, + AdUnitCode: bid.ImpID, + Price: bid.Price, + Creative_id: bid.CrID, + BidderCode: bidder.BidderCode, + } + + if imp.Video != nil { + pbsBid.CreativeMediaType = "video" + pbsBid.NURL = bid.AdM // Assign to NURL so it'll be interpreted as a vastUrl + pbsBid.Width = imp.Video.W + pbsBid.Height = imp.Video.H + } else { + pbsBid.CreativeMediaType = "banner" + pbsBid.NURL = bid.NURL + pbsBid.Adm = bid.AdM + pbsBid.Width = bid.W + pbsBid.Height = bid.H + } + + bids = append(bids, &pbsBid) + } + } + + if len(bids) == 0 { + return nil, nil + } + + return bids, nil +} + +func NewConversantLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *ConversantLegacyAdapter { + a := adapters.NewHTTPAdapter(config) + + return &ConversantLegacyAdapter{ + http: a, + URI: uri, + } +} diff --git a/adapters/conversant/cnvr_legacy_test.go b/adapters/conversant/cnvr_legacy_test.go new file mode 100644 index 00000000000..fc34a93fae2 --- /dev/null +++ b/adapters/conversant/cnvr_legacy_test.go @@ -0,0 +1,853 @@ +package conversant + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" +) + +// Constants + +const ExpectedSiteID string = "12345" +const ExpectedDisplayManager string = "prebid-s2s" +const ExpectedBuyerUID string = "AQECT_o7M1FLbQJK8QFmAQEBAQE" +const ExpectedNURL string = "http://test.dotomi.com" +const ExpectedAdM string = "" +const ExpectedCrID string = "98765" + +const DefaultParam = `{"site_id": "12345"}` + +// Test properties of Adapter interface + +func TestConversantProperties(t *testing.T) { + an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") + + assertNotEqual(t, an.Name(), "", "Missing family name") + assertTrue(t, an.SkipNoCookies(), "SkipNoCookies should be true") +} + +// Test empty bid requests + +func TestConversantEmptyBid(t *testing.T) { + an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{} + _, err := an.Call(ctx, &pbReq, &pbBidder) + assertTrue(t, err != nil, "No error received for an invalid request") +} + +// Test required parameters, which is just the site id for now + +func TestConversantRequiredParameters(t *testing.T) { + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) + }), + ) + defer server.Close() + + an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + ctx := context.TODO() + + testParams := func(params ...string) (pbs.PBSBidSlice, error) { + req, err := CreateBannerRequest(params...) + if err != nil { + return nil, err + } + return an.Call(ctx, req, req.Bidders[0]) + } + + var err error + + if _, err = testParams(`{}`); err == nil { + t.Fatal("Failed to catch missing site id") + } +} + +// Test handling of 404 + +func TestConversantBadStatus(t *testing.T) { + // Create a test http server that returns after 2 milliseconds + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + }), + ) + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + assertTrue(t, err != nil, "Failed to catch 404 error") +} + +// Test handling of HTTP timeout + +func TestConversantTimeout(t *testing.T) { + // Create a test http server that returns after 2 milliseconds + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + <-time.After(2 * time.Millisecond) + }), + ) + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + // Create a context that expires before http returns + + ctx, cancel := context.WithTimeout(context.Background(), 0) + defer cancel() + + // Create a basic request + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + // Attempt to process the request, which should hit a timeout + // immediately + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err == nil || err != context.DeadlineExceeded { + t.Fatal("No timeout recevied for timed out request", err) + } +} + +// Test handling of 204 + +func TestConversantNoBid(t *testing.T) { + // Create a test http server that returns after 2 milliseconds + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) + }), + ) + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + if resp != nil || err != nil { + t.Fatal("Failed to handle empty bid", err) + } +} + +// Verify an outgoing openrtp request is created correctly + +func TestConversantRequestDefault(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Video == nil, "Request video should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") + assertEqual(t, imp.TagID, "", "Request tag id") + assertTrue(t, imp.Banner.Pos == nil, "Request pos") + assertEqual(t, int(*imp.Banner.W), 300, "Request width") + assertEqual(t, int(*imp.Banner.H), 250, "Request height") +} + +// Verify inapp video request +func TestConversantInappVideoRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + requestParam := `{"secure": 1, "site_id": "12345"}` + appParam := `{ "bundle": "com.naver.linewebtoon" }` + videoParam := `{ "mimes": ["video/x-ms-wmv"], + "protocols": [1, 2], + "maxduration": 90 }` + + ctx := context.TODO() + pbReq := CreateRequest(requestParam) + pbReq, err := ConvertToVideoRequest(pbReq, videoParam) + if err != nil { + t.Fatal("failed to parse request") + } + pbReq, err = ConvertToAppRequest(pbReq, appParam) + if err != nil { + t.Fatal("failed to parse request") + } + pbReq, err = ParseRequest(pbReq) + if err != nil { + t.Fatal("failed to parse request") + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + + imp := &lastReq.Imp[0] + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + assertEqual(t, lastReq.App.ID, "12345", "App Id") +} + +// Verify inapp video request +func TestConversantInappBannerRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + param := `{ "secure": 1, + "site_id": "12345", + "tag_id": "top", + "position": 2, + "bidfloor": 1.01 }` + appParam := `{ "bundle": "com.naver.linewebtoon" }` + + ctx := context.TODO() + pbReq, _ := CreateBannerRequest(param) + pbReq, err := ConvertToAppRequest(pbReq, appParam) + if err != nil { + t.Fatal("failed to parse request") + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + + imp := &lastReq.Imp[0] + assertEqual(t, lastReq.App.ID, "12345", "App Id") + assertEqual(t, int(*imp.Banner.W), 300, "Request width") + assertEqual(t, int(*imp.Banner.H), 250, "Request height") +} + +// Verify an outgoing openrtp request with additional conversant parameters is +// processed correctly + +func TestConversantRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "secure": 1, + "tag_id": "top", + "position": 2, + "bidfloor": 1.01, + "mobile": 1 }` + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(param) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 1, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Video == nil, "Request video should be nil") + assertEqual(t, int(*imp.Secure), 1, "Request secure") + assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") + assertEqual(t, imp.TagID, "top", "Request tag id") + assertEqual(t, int(*imp.Banner.Pos), 2, "Request pos") + assertEqual(t, int(*imp.Banner.W), 300, "Request width") + assertEqual(t, int(*imp.Banner.H), 250, "Request height") +} + +// Verify openrtp responses are converted correctly + +func TestConversantResponse(t *testing.T) { + prices := []float64{0.01, 0.0, 2.01} + server, lastReq := CreateServer(prices...) + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "secure": 1, + "tag_id": "top", + "position": 2, + "bidfloor": 1.01, + "mobile" : 1}` + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(param, param, param) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + prices, imps := FilterZeroPrices(prices, lastReq.Imp) + + assertEqual(t, len(resp), len(prices), "Bad number of responses") + + for i, bid := range resp { + assertEqual(t, bid.Price, prices[i], "Bad price in response") + assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") + + if bid.Price > 0 { + assertEqual(t, bid.Adm, ExpectedAdM, "Bad ad markup in response") + assertEqual(t, bid.NURL, ExpectedNURL, "Bad notification url in response") + assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") + assertEqual(t, bid.Width, *imps[i].Banner.W, "Bad width in response") + assertEqual(t, bid.Height, *imps[i].Banner.H, "Bad height in response") + } + } +} + +// Test video request + +func TestConversantBasicVideoRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "tag_id": "bottom left", + "position": 3, + "bidfloor": 1.01 }` + + ctx := context.TODO() + pbReq, err := CreateVideoRequest(param) + if err != nil { + t.Fatal("Failed to create a video request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Banner == nil, "Request banner should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") + assertEqual(t, imp.TagID, "bottom left", "Request tag id") + assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + + assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") + assertEqual(t, imp.Video.MIMEs[0], "video/mp4", "Requst video MIMEs type") + assertTrue(t, imp.Video.Protocols == nil, "Request video protocols") + assertEqual(t, imp.Video.MaxDuration, int64(0), "Request video 0 max duration") + assertTrue(t, imp.Video.API == nil, "Request video api should be nil") +} + +// Test video request with parameters in custom params object + +func TestConversantVideoRequestWithParams(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "tag_id": "bottom left", + "position": 3, + "bidfloor": 1.01, + "mimes": ["video/x-ms-wmv"], + "protocols": [1, 2], + "api": [1, 2], + "maxduration": 90 }` + + ctx := context.TODO() + pbReq, err := CreateVideoRequest(param) + if err != nil { + t.Fatal("Failed to create a video request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Banner == nil, "Request banner should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") + assertEqual(t, imp.TagID, "bottom left", "Request tag id") + assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + + assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") + assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") + assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") + assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") + assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") + assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") + assertEqual(t, len(imp.Video.API), 2, "Request video api should be nil") + assertEqual(t, imp.Video.API[0], openrtb2.APIFramework(1), "Request video api 1") + assertEqual(t, imp.Video.API[1], openrtb2.APIFramework(2), "Request video api 2") +} + +// Test video request with parameters in the video object + +func TestConversantVideoRequestWithParams2(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + param := `{ "site_id": "12345" }` + videoParam := `{ "mimes": ["video/x-ms-wmv"], + "protocols": [1, 2], + "maxduration": 90 }` + + ctx := context.TODO() + pbReq := CreateRequest(param) + pbReq, err := ConvertToVideoRequest(pbReq, videoParam) + if err != nil { + t.Fatal("Failed to convert to a video request", err) + } + pbReq, err = ParseRequest(pbReq) + if err != nil { + t.Fatal("Failed to parse video request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Banner == nil, "Request banner should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + + assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") + assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") + assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") + assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") + assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") + assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") +} + +// Test video responses + +func TestConversantVideoResponse(t *testing.T) { + prices := []float64{0.01, 0.0, 2.01} + server, lastReq := CreateServer(prices...) + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantLegacyAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "secure": 1, + "tag_id": "top", + "position": 2, + "bidfloor": 1.01, + "mobile" : 1}` + + ctx := context.TODO() + pbReq, err := CreateVideoRequest(param, param, param) + if err != nil { + t.Fatal("Failed to create a video request", err) + } + + resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + prices, imps := FilterZeroPrices(prices, lastReq.Imp) + + assertEqual(t, len(resp), len(prices), "Bad number of responses") + + for i, bid := range resp { + assertEqual(t, bid.Price, prices[i], "Bad price in response") + assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") + + if bid.Price > 0 { + assertEqual(t, bid.Adm, "", "Bad ad markup in response") + assertEqual(t, bid.NURL, ExpectedAdM, "Bad notification url in response") + assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") + assertEqual(t, bid.Width, imps[i].Video.W, "Bad width in response") + assertEqual(t, bid.Height, imps[i].Video.H, "Bad height in response") + } + } +} + +// Helpers to create a banner and video requests + +func CreateRequest(params ...string) *pbs.PBSRequest { + num := len(params) + + req := pbs.PBSRequest{ + Tid: "t-000", + AccountID: "1", + AdUnits: make([]pbs.AdUnit, num), + } + + for i := 0; i < num; i++ { + req.AdUnits[i] = pbs.AdUnit{ + Code: fmt.Sprintf("au-%03d", i), + Sizes: []openrtb2.Format{ + { + W: 300, + H: 250, + }, + }, + Bids: []pbs.Bids{ + { + BidderCode: "conversant", + BidID: fmt.Sprintf("b-%03d", i), + Params: json.RawMessage(params[i]), + }, + }, + } + } + + return &req +} + +// Convert a request to a video request by adding required properties + +func ConvertToVideoRequest(req *pbs.PBSRequest, videoParams ...string) (*pbs.PBSRequest, error) { + for i := 0; i < len(req.AdUnits); i++ { + video := pbs.PBSVideo{} + if i < len(videoParams) { + err := json.Unmarshal([]byte(videoParams[i]), &video) + if err != nil { + return nil, err + } + } + + if video.Mimes == nil { + video.Mimes = []string{"video/mp4"} + } + + req.AdUnits[i].Video = video + req.AdUnits[i].MediaTypes = []string{"video"} + } + + return req, nil +} + +// Convert a request to an app request by adding required properties +func ConvertToAppRequest(req *pbs.PBSRequest, appParams string) (*pbs.PBSRequest, error) { + app := new(openrtb2.App) + err := json.Unmarshal([]byte(appParams), &app) + if err == nil { + req.App = app + } + + return req, nil +} + +// Feed the request thru the prebid parser so user id and +// other private properties are defined + +func ParseRequest(req *pbs.PBSRequest) (*pbs.PBSRequest, error) { + body := new(bytes.Buffer) + _ = json.NewEncoder(body).Encode(req) + + // Need to pass the conversant user id thru uid cookie + + httpReq := httptest.NewRequest("POST", "/foo", body) + cookie := usersync.NewCookie() + _ = cookie.TrySync("conversant", ExpectedBuyerUID) + httpReq.Header.Set("Cookie", cookie.ToHTTPCookie(90*24*time.Hour).String()) + httpReq.Header.Add("Referer", "http://example.com") + cache, _ := dummycache.New() + hcc := config.HostCookie{} + + parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, cache, &hcc) + + return parsedReq, err +} + +// A helper to create a banner request + +func CreateBannerRequest(params ...string) (*pbs.PBSRequest, error) { + req := CreateRequest(params...) + req, err := ParseRequest(req) + return req, err +} + +// A helper to create a video request + +func CreateVideoRequest(params ...string) (*pbs.PBSRequest, error) { + req := CreateRequest(params...) + req, err := ConvertToVideoRequest(req) + if err != nil { + return nil, err + } + req, err = ParseRequest(req) + return req, err +} + +// Helper to create a test http server that receives and generate openrtb requests and responses + +func CreateServer(prices ...float64) (*httptest.Server, *openrtb2.BidRequest) { + var lastBidRequest openrtb2.BidRequest + server := httptest.NewServer(http.HandlerFunc(func(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 bidReq openrtb2.BidRequest + var price float64 + var bids []openrtb2.Bid + var bid openrtb2.Bid + + err = json.Unmarshal(body, &bidReq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + lastBidRequest = bidReq + + for i, imp := range bidReq.Imp { + if i < len(prices) { + price = prices[i] + } else { + price = 0 + } + + if price > 0 { + bid = openrtb2.Bid{ + ID: imp.ID, + ImpID: imp.ID, + Price: price, + NURL: ExpectedNURL, + AdM: ExpectedAdM, + CrID: ExpectedCrID, + } + + if imp.Banner != nil { + bid.W = *imp.Banner.W + bid.H = *imp.Banner.H + } else if imp.Video != nil { + bid.W = imp.Video.W + bid.H = imp.Video.H + } + } else { + bid = openrtb2.Bid{ + ID: imp.ID, + ImpID: imp.ID, + Price: 0, + } + } + + bids = append(bids, bid) + } + + if len(bids) == 0 { + w.WriteHeader(http.StatusNoContent) + } else { + js, _ := json.Marshal(openrtb2.BidResponse{ + ID: bidReq.ID, + SeatBid: []openrtb2.SeatBid{ + { + Bid: bids, + }, + }, + }) + + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(js) + } + }), + ) + + return server, &lastBidRequest +} + +// Helper to remove impressions with $0 bids + +func FilterZeroPrices(prices []float64, imps []openrtb2.Imp) ([]float64, []openrtb2.Imp) { + prices2 := make([]float64, 0) + imps2 := make([]openrtb2.Imp, 0) + + for i := range prices { + if prices[i] > 0 { + prices2 = append(prices2, prices[i]) + imps2 = append(imps2, imps[i]) + } + } + + return prices2, imps2 +} + +// Helpers to test equality + +func assertEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { + if expected != actual { + msg = fmt.Sprintf("%s: act(%v) != exp(%v)", msg, actual, expected) + t.Fatal(msg) + } +} + +func assertNotEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { + if expected == actual { + msg = fmt.Sprintf("%s: act(%v) == exp(%v)", msg, actual, expected) + t.Fatal(msg) + } +} + +func assertTrue(t *testing.T, val bool, msg string) { + if val == false { + msg = fmt.Sprintf("%s: is false but should be true", msg) + t.Fatal(msg) + } +} + +func assertFalse(t *testing.T, val bool, msg string) { + if val == true { + msg = fmt.Sprintf("%s: is true but should be false", msg) + t.Fatal(msg) + } +} diff --git a/adapters/deepintent/deepintent.go b/adapters/deepintent/deepintent.go index 0853bb8b405..b5b0fd54c5d 100644 --- a/adapters/deepintent/deepintent.go +++ b/adapters/deepintent/deepintent.go @@ -20,6 +20,10 @@ type DeepintentAdapter struct { URI string } +type deepintentParams struct { + tagId string `json:"tagId"` +} + // Builder builds a new instance of the Deepintent adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &DeepintentAdapter{ diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index aa4a6f79053..409290c110d 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -13,6 +13,10 @@ import ( "github.com/prebid/prebid-server/adapters/adapterstest" ) +var ( + bidRequest string +) + func TestFetchParams(t *testing.T) { var w, h int = 300, 250 diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index 62bcc08d7cb..375248137ad 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -178,6 +178,7 @@ func TestImpFiltering(t *testing.T) { } type mockBidder struct { + gotRequest *openrtb2.BidRequest } func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 1cfec69322d..c79eda31040 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -1,27 +1,259 @@ package ix import ( + "bytes" + "context" "encoding/json" "fmt" + "io/ioutil" "net/http" "sort" "strings" + "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" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - - "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/pbs" + "golang.org/x/net/context/ctxhttp" ) type IxAdapter struct { + http *adapters.HTTPAdapter URI string maxRequests int } +func (a *IxAdapter) Name() string { + return string(openrtb_ext.BidderIx) +} + +func (a *IxAdapter) SkipNoCookies() bool { + return false +} + +type indexParams struct { + SiteID string `json:"siteId"` +} + +type ixBidResult struct { + Request *callOneObject + StatusCode int + ResponseBody string + Bid *pbs.PBSBid + Error error +} + +type callOneObject struct { + requestJSON bytes.Buffer + width int64 + height int64 + bidType string +} + +func (a *IxAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + var prioritizedRequests, requests []callOneObject + + mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + indexReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) + if err != nil { + return nil, err + } + + indexReqImp := indexReq.Imp + for i, unit := range bidder.AdUnits { + // Supposedly fixes some segfaults + if len(indexReqImp) <= i { + break + } + + var params indexParams + err := json.Unmarshal(unit.Params, ¶ms) + if err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("unmarshal params '%s' failed: %v", unit.Params, err), + } + } + + if params.SiteID == "" { + return nil, &errortypes.BadInput{ + Message: "Missing siteId param", + } + } + + for sizeIndex, format := range unit.Sizes { + // Only grab this ad unit. Not supporting multi-media-type adunit yet. + thisImp := indexReqImp[i] + + thisImp.TagID = unit.Code + if thisImp.Banner != nil { + thisImp.Banner.Format = []openrtb2.Format{format} + thisImp.Banner.W = &format.W + thisImp.Banner.H = &format.H + } + indexReq.Imp = []openrtb2.Imp{thisImp} + // Index spec says "adunit path representing ad server inventory" but we don't have this + // ext is DFP div ID and KV pairs if avail + //indexReq.Imp[i].Ext = json.RawMessage("{}") + + if indexReq.Site != nil { + // Any objects pointed to by indexReq *must not be mutated*, or we will get race conditions. + siteCopy := *indexReq.Site + siteCopy.Publisher = &openrtb2.Publisher{ID: params.SiteID} + indexReq.Site = &siteCopy + } + + bidType := "" + if thisImp.Banner != nil { + bidType = string(openrtb_ext.BidTypeBanner) + } else if thisImp.Video != nil { + bidType = string(openrtb_ext.BidTypeVideo) + } + j, _ := json.Marshal(indexReq) + request := callOneObject{requestJSON: *bytes.NewBuffer(j), width: format.W, height: format.H, bidType: bidType} + + // prioritize slots over sizes + if sizeIndex == 0 { + prioritizedRequests = append(prioritizedRequests, request) + } else { + requests = append(requests, request) + } + } + } + + // cap the number of requests to maxRequests + requests = append(prioritizedRequests, requests...) + if len(requests) > a.maxRequests { + requests = requests[:a.maxRequests] + } + + if len(requests) == 0 { + return nil, &errortypes.BadInput{ + Message: "Invalid ad unit/imp/size", + } + } + + ch := make(chan ixBidResult) + for _, request := range requests { + go func(bidder *pbs.PBSBidder, request callOneObject) { + result, err := a.callOne(ctx, request.requestJSON) + result.Request = &request + result.Error = err + if result.Bid != nil { + result.Bid.BidderCode = bidder.BidderCode + result.Bid.BidID = bidder.LookupBidID(result.Bid.AdUnitCode) + result.Bid.Width = request.width + result.Bid.Height = request.height + result.Bid.CreativeMediaType = request.bidType + + 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, request) + } + + bids := make(pbs.PBSBidSlice, 0) + for i := 0; i < len(requests); i++ { + result := <-ch + if result.Bid != nil && result.Bid.Price != 0 { + bids = append(bids, result.Bid) + } + + if req.IsDebug { + debug := &pbs.BidderDebug{ + RequestURI: a.URI, + RequestBody: result.Request.requestJSON.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 (a *IxAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (ixBidResult, error) { + var result ixBidResult + + httpReq, _ := http.NewRequest("POST", a.URI, &reqJSON) + httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") + httpReq.Header.Add("Accept", "application/json") + + ixResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) + if err != nil { + return result, err + } + + result.StatusCode = ixResp.StatusCode + + if ixResp.StatusCode == http.StatusNoContent { + return result, nil + } + + if ixResp.StatusCode == http.StatusBadRequest { + return result, &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), + } + } + + if ixResp.StatusCode != http.StatusOK { + return result, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), + } + } + + defer ixResp.Body.Close() + body, err := ioutil.ReadAll(ixResp.Body) + if err != nil { + return result, err + } + result.ResponseBody = string(body) + + var bidResp openrtb2.BidResponse + err = json.Unmarshal(body, &bidResp) + if err != nil { + return result, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Error parsing response: %v", err), + } + } + + if len(bidResp.SeatBid) == 0 { + return result, nil + } + if len(bidResp.SeatBid[0].Bid) == 0 { + return result, nil + } + bid := bidResp.SeatBid[0].Bid[0] + + pbid := pbs.PBSBid{ + AdUnitCode: bid.ImpID, + Price: bid.Price, + Adm: bid.AdM, + Creative_id: bid.CrID, + Width: bid.W, + Height: bid.H, + DealId: bid.DealID, + } + + result.Bid = &pbid + return result, nil +} + func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { nImp := len(request.Imp) if nImp > a.maxRequests { @@ -222,6 +454,14 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque return bidderResponse, errs } +func NewIxLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *IxAdapter { + return &IxAdapter{ + http: adapters.NewHTTPAdapter(config), + URI: endpoint, + maxRequests: 20, + } +} + // Builder builds a new instance of the Ix adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &IxAdapter{ diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index fc1d0f9a0a2..d292273a92c 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -1,15 +1,22 @@ package ix import ( + "context" "encoding/json" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "net/http/httptest" "testing" + "time" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/pbs" ) const endpoint string = "http://host/endpoint" @@ -24,6 +31,698 @@ func TestJsonSamples(t *testing.T) { } } +// Tests for the legacy, non-openrtb code. +// They can be removed after the legacy interface is deprecated. + +func getAdUnit() pbs.PBSAdUnit { + return pbs.PBSAdUnit{ + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + BidID: "bidid", + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + Params: json.RawMessage("{\"siteId\":\"12\"}"), + } +} + +func getVideoAdUnit() pbs.PBSAdUnit { + return pbs.PBSAdUnit{ + Code: "unitCodeVideo", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, + BidID: "bididvideo", + Sizes: []openrtb2.Format{ + { + W: 100, + H: 75, + }, + }, + Video: pbs.PBSVideo{ + Mimes: []string{"video/mp4"}, + Minduration: 15, + Maxduration: 30, + Startdelay: 5, + Skippable: 0, + PlaybackMethod: 1, + Protocols: []int8{2, 3}, + }, + Params: json.RawMessage("{\"siteId\":\"12\"}"), + } +} + +func getOpenRTBBid(i openrtb2.Imp) openrtb2.Bid { + return openrtb2.Bid{ + ID: fmt.Sprintf("%d", rand.Intn(1000)), + ImpID: i.ID, + Price: 1.0, + AdM: "Content", + } +} + +func newAdapter(endpoint string) *IxAdapter { + return NewIxLegacyAdapter(adapters.DefaultHTTPAdapterConfig, endpoint) +} + +func dummyIXServer(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 + } + + impression := breq.Imp[0] + + resp := openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + getOpenRTBBid(impression), + }, + }, + }, + } + + 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 TestIxSkipNoCookies(t *testing.T) { + if newAdapter(endpoint).SkipNoCookies() { + t.Fatalf("SkipNoCookies must return false") + } +} + +func TestIxInvalidCall(t *testing.T) { + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{} + _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("No error received for invalid request") + } +} + +func TestIxInvalidCallReqAppNil(t *testing.T) { + ctx := context.TODO() + pbReq := pbs.PBSRequest{ + App: &openrtb2.App{}, + } + pbBidder := pbs.PBSBidder{} + + _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("No error received for invalid request") + } +} + +func TestIxInvalidCallMissingSiteID(t *testing.T) { + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.Params = json.RawMessage("{}") + + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit, + }, + } + _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("No error received for request with missing siteId") + } +} + +func TestIxTimeout(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + <-time.After(2 * time.Millisecond) + }), + ) + defer server.Close() + + ctx, cancel := context.WithTimeout(context.Background(), 0) + defer cancel() + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), + }, + } + _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) + if err == nil || err != context.DeadlineExceeded { + t.Fatalf("Invalid timeout error received") + } +} + +func TestIxTimeoutMultipleSlots(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + + var breq openrtb2.BidRequest + err = json.Unmarshal(body, &breq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + impression := breq.Imp[0] + + resp := openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + getOpenRTBBid(impression), + }, + }, + }, + } + + js, err := json.Marshal(resp) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // cancel the request before 2nd impression is returned + // delay to let 1st impression return successfully + if impression.ID == "unitCode2" { + <-time.After(10 * time.Millisecond) + cancel() + <-r.Context().Done() + } + + w.Header().Set("Content-Type", "application/json") + w.Write(js) + }), + ) + defer server.Close() + + pbReq := pbs.PBSRequest{} + + adUnit1 := getAdUnit() + adUnit2 := getAdUnit() + adUnit2.Code = "unitCode2" + adUnit2.Sizes = []openrtb2.Format{ + { + W: 8, + H: 10, + }, + } + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit1, + adUnit2, + }, + } + bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) + + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + + if len(bids) != 1 { + t.Fatalf("Should have received one bid") + } + + bid := findBidByAdUnitCode(bids, adUnit1.Code) + if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { + t.Fatalf("Received the wrong size") + } +} + +func TestIxInvalidJsonResponse(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Blah") + }), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), + }, + } + _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("No error received for invalid request") + } +} + +func TestIxInvalidStatusCode(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Send 404 + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + }), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{IsDebug: true} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), + }, + } + _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("No error received for invalid request") + } +} + +func TestIxBadRequest(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Send 400 + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + }), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), + }, + } + _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("No error received for bad request") + } +} + +func TestIxNoContent(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Send 204 + http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) + }), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), + }, + } + + bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) + if err != nil || bids != nil { + t.Fatalf("Must return nil for no content") + } +} + +func TestIxInvalidCallMissingSize(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.Sizes = []openrtb2.Format{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit, + }, + } + if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { + t.Fatalf("Should not have gotten an error for missing/invalid size: %v", err) + } +} + +func TestIxInvalidCallEmptyBidIDResponse(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.BidID = "" + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit, + }, + } + if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { + t.Fatalf("Should have gotten an error for unknown adunit code") + } +} + +func TestIxMismatchUnitCode(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + + var breq openrtb2.BidRequest + err = json.Unmarshal(body, &breq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resp := openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: []openrtb2.Bid{ + { + ID: fmt.Sprintf("%d", rand.Intn(1000)), + ImpID: "unitCode_bogus", + Price: 1.0, + AdM: "Content", + W: 10, + H: 12, + }, + }, + }, + }, + } + + 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) + }), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), + }, + } + if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { + t.Fatalf("Should have gotten an error for unknown adunit code") + } +} + +func TestNoSeatBid(t *testing.T) { + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + + var breq openrtb2.BidRequest + err = json.Unmarshal(body, &breq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resp := openrtb2.BidResponse{} + + 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) + }), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), + }, + } + if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } +} + +func TestNoSeatBidBid(t *testing.T) { + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + + var breq openrtb2.BidRequest + err = json.Unmarshal(body, &breq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resp := openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + {}, + }, + } + + 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) + }), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), + }, + } + if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } +} + +func TestIxInvalidParam(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.Params = json.RawMessage("Bogus invalid input") + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit, + }, + } + if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { + t.Fatalf("Should have gotten an error for unrecognized params") + } +} + +func TestIxSingleSlotSingleValidSize(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), + }, + } + bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + + if len(bids) != 1 { + t.Fatalf("Should have received one bid") + } +} + +func TestIxTwoSlotValidSize(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit1 := getAdUnit() + adUnit2 := getVideoAdUnit() + adUnit2.Params = json.RawMessage("{\"siteId\":\"1111\"}") + + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit1, + adUnit2, + }, + } + bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + + if len(bids) != 2 { + t.Fatalf("Should have received two bid") + } + + bid := findBidByAdUnitCode(bids, adUnit1.Code) + if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { + t.Fatalf("Received the wrong size") + } + + bid = findBidByAdUnitCode(bids, adUnit2.Code) + if adUnit2.Sizes[0].H != bid.Height || adUnit2.Sizes[0].W != bid.Width { + t.Fatalf("Received the wrong size") + } +} + +func TestIxTwoSlotMultiSizeOnlyValidIXSizeResponse(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.Sizes = append(adUnit.Sizes, openrtb2.Format{W: 20, H: 22}) + + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit, + }, + } + bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + + if len(bids) != 2 { + t.Fatalf("Should have received 2 bids") + } + + for _, size := range adUnit.Sizes { + if !bidResponseForSizeExist(bids, size.H, size.W) { + t.Fatalf("Missing bid for specified size %d and %d", size.W, size.H) + } + } +} + +func bidResponseForSizeExist(bids pbs.PBSBidSlice, h, w int64) bool { + for _, v := range bids { + if v.Height == h && v.Width == w { + return true + } + } + return false +} + +func findBidByAdUnitCode(bids pbs.PBSBidSlice, c string) *pbs.PBSBid { + for _, v := range bids { + if v.AdUnitCode == c { + return v + } + } + return &pbs.PBSBid{} +} + +func TestIxMaxRequests(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + adapter := newAdapter(server.URL) + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnits := []pbs.PBSAdUnit{} + + for i := 0; i < adapter.maxRequests+1; i++ { + adUnits = append(adUnits, getAdUnit()) + } + + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: adUnits, + } + + bids, err := adapter.Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + + if len(bids) != adapter.maxRequests { + t.Fatalf("Should have received %d bid", adapter.maxRequests) + } +} + func TestIxMakeBidsWithCategoryDuration(t *testing.T) { bidder := &IxAdapter{} diff --git a/adapters/legacy.go b/adapters/legacy.go new file mode 100644 index 00000000000..8b2221fe0ca --- /dev/null +++ b/adapters/legacy.go @@ -0,0 +1,97 @@ +package adapters + +import ( + "context" + "crypto/tls" + "net/http" + "time" + + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/server/ssl" +) + +// This file contains some deprecated, legacy types. +// +// These support the `/auction` endpoint, but will be replaced by `/openrtb2/auction`. +// New demand partners should ignore this file, and implement the Bidder interface. + +// Adapter is a deprecated interface which connects prebid-server to a demand partner. +// PBS is currently being rewritten to use Bidder, and this will be removed after. +// Their primary purpose is to produce bids in response to Auction requests. +type Adapter interface { + // Name must be identical to the BidderName. + Name() string + // Determines whether this adapter should get callouts if there is not a synched user ID. + SkipNoCookies() bool + // Call produces bids which should be considered, given the auction params. + // + // In practice, implementations almost always make one call to an external server here. + // However, that is not a requirement for satisfying this interface. + // + // An error here will cause all bids to be ignored. If the error was caused by bad user input, + // this should return a BadInputError. If it was caused by bad server behavior + // (e.g. 500, unexpected response format, etc), this should return a BadServerResponseError. + Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) +} + +// HTTPAdapterConfig groups options which control how HTTP requests are made by adapters. +type HTTPAdapterConfig struct { + // See IdleConnTimeout on https://golang.org/pkg/net/http/#Transport + IdleConnTimeout time.Duration + // See MaxIdleConns on https://golang.org/pkg/net/http/#Transport + MaxConns int + // See MaxIdleConnsPerHost on https://golang.org/pkg/net/http/#Transport + MaxConnsPerHost int +} + +type HTTPAdapter struct { + Client *http.Client +} + +// DefaultHTTPAdapterConfig is an HTTPAdapterConfig that chooses sensible default values. +var DefaultHTTPAdapterConfig = &HTTPAdapterConfig{ + MaxConns: 50, + MaxConnsPerHost: 10, + IdleConnTimeout: 60 * time.Second, +} + +// NewHTTPAdapter creates an HTTPAdapter which obeys the rules given by the config, and +// has all the available SSL certs available in the project. +func NewHTTPAdapter(c *HTTPAdapterConfig) *HTTPAdapter { + ts := &http.Transport{ + MaxIdleConns: c.MaxConns, + MaxIdleConnsPerHost: c.MaxConnsPerHost, + IdleConnTimeout: c.IdleConnTimeout, + TLSClientConfig: &tls.Config{RootCAs: ssl.GetRootCAPool()}, + } + + return &HTTPAdapter{ + Client: &http.Client{ + Transport: ts, + }, + } +} + +// used for callOne (possibly pull all of the shared code here) +type CallOneResult struct { + StatusCode int + ResponseBody string + Bid *pbs.PBSBid + Error error +} + +type MisconfiguredAdapter struct { + TheName string + Err error +} + +func (b *MisconfiguredAdapter) Name() string { + return b.TheName +} +func (b *MisconfiguredAdapter) SkipNoCookies() bool { + return false +} + +func (b *MisconfiguredAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + return nil, b.Err +} diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go new file mode 100644 index 00000000000..6aa07c6b764 --- /dev/null +++ b/adapters/openrtb_util.go @@ -0,0 +1,174 @@ +package adapters + +import ( + "encoding/json" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/pbs" +) + +func min(x, y int) int { + if x < y { + return x + } + return y +} + +func mediaTypeInSlice(t pbs.MediaType, list []pbs.MediaType) bool { + for _, b := range list { + if b == t { + return true + } + } + return false +} + +func commonMediaTypes(l1 []pbs.MediaType, l2 []pbs.MediaType) []pbs.MediaType { + res := make([]pbs.MediaType, min(len(l1), len(l2))) + i := 0 + for _, b := range l1 { + if mediaTypeInSlice(b, l2) { + res[i] = b + i = i + 1 + } + } + return res[:i] +} + +func makeBanner(unit pbs.PBSAdUnit) *openrtb2.Banner { + return &openrtb2.Banner{ + W: openrtb2.Int64Ptr(unit.Sizes[0].W), + H: openrtb2.Int64Ptr(unit.Sizes[0].H), + Format: copyFormats(unit.Sizes), // defensive copy because adapters may mutate Imps, and this is shared data + TopFrame: unit.TopFrame, + } +} + +func makeVideo(unit pbs.PBSAdUnit) *openrtb2.Video { + // empty mimes array is a sign of uninitialized Video object + if len(unit.Video.Mimes) < 1 { + return nil + } + mimes := make([]string, len(unit.Video.Mimes)) + copy(mimes, unit.Video.Mimes) + pbm := make([]openrtb2.PlaybackMethod, 1) + //this will become int8 soon, so we only care about the first index in the array + pbm[0] = openrtb2.PlaybackMethod(unit.Video.PlaybackMethod) + + protocols := make([]openrtb2.Protocol, 0, len(unit.Video.Protocols)) + for _, protocol := range unit.Video.Protocols { + protocols = append(protocols, openrtb2.Protocol(protocol)) + } + return &openrtb2.Video{ + MIMEs: mimes, + MinDuration: unit.Video.Minduration, + MaxDuration: unit.Video.Maxduration, + W: unit.Sizes[0].W, + H: unit.Sizes[0].H, + StartDelay: openrtb2.StartDelay(unit.Video.Startdelay).Ptr(), + PlaybackMethod: pbm, + Protocols: protocols, + } +} + +// adapters.MakeOpenRTBGeneric makes an openRTB request from the PBS-specific structs. +// +// Any objects pointed to by the returned BidRequest *must not be mutated*, or we will get race conditions. +// The only exception is the Imp property, whose objects will be created new by this method and can be mutated freely. +func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily string, allowedMediatypes []pbs.MediaType) (openrtb2.BidRequest, error) { + imps := make([]openrtb2.Imp, 0, len(bidder.AdUnits)*len(allowedMediatypes)) + for _, unit := range bidder.AdUnits { + if len(unit.Sizes) <= 0 { + continue + } + unitMediaTypes := commonMediaTypes(unit.MediaTypes, allowedMediatypes) + if len(unitMediaTypes) == 0 { + continue + } + + newImp := openrtb2.Imp{ + ID: unit.Code, + Secure: &req.Secure, + Instl: unit.Instl, + } + for _, mType := range unitMediaTypes { + switch mType { + case pbs.MEDIA_TYPE_BANNER: + newImp.Banner = makeBanner(unit) + case pbs.MEDIA_TYPE_VIDEO: + newImp.Video = makeVideo(unit) + // It's strange to error here... but preserves legacy behavior in legacy code. See #603. + if newImp.Video == nil { + return openrtb2.BidRequest{}, &errortypes.BadInput{ + Message: "Invalid AdUnit: VIDEO media type with no video data", + } + } + } + } + if newImp.Banner != nil || newImp.Video != nil { + imps = append(imps, newImp) + } + } + + if len(imps) < 1 { + return openrtb2.BidRequest{}, &errortypes.BadInput{ + Message: "openRTB bids need at least one Imp", + } + } + + if req.App != nil { + return openrtb2.BidRequest{ + ID: req.Tid, + Imp: imps, + App: req.App, + Device: req.Device, + User: req.User, + Source: &openrtb2.Source{ + TID: req.Tid, + }, + AT: 1, + TMax: req.TimeoutMillis, + Regs: req.Regs, + }, nil + } + + buyerUID, _, _ := req.Cookie.GetUID(bidderFamily) + id, _, _ := req.Cookie.GetUID("adnxs") + + var userExt json.RawMessage + if req.User != nil { + userExt = req.User.Ext + } + + return openrtb2.BidRequest{ + ID: req.Tid, + Imp: imps, + Site: &openrtb2.Site{ + Domain: req.Domain, + Page: req.Url, + }, + Device: req.Device, + User: &openrtb2.User{ + BuyerUID: buyerUID, + ID: id, + Ext: userExt, + }, + Source: &openrtb2.Source{ + FD: 1, // upstream, aka header + TID: req.Tid, + }, + AT: 1, + TMax: req.TimeoutMillis, + Regs: req.Regs, + }, nil +} + +func copyFormats(sizes []openrtb2.Format) []openrtb2.Format { + sizesCopy := make([]openrtb2.Format, len(sizes)) + for i := 0; i < len(sizes); i++ { + sizesCopy[i] = sizes[i] + sizesCopy[i].Ext = append([]byte(nil), sizes[i].Ext...) + } + return sizesCopy +} diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go new file mode 100644 index 00000000000..035f4d9b679 --- /dev/null +++ b/adapters/openrtb_util_test.go @@ -0,0 +1,543 @@ +package adapters + +import ( + "testing" + + "encoding/json" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" + "github.com/stretchr/testify/assert" +) + +func TestCommonMediaTypes(t *testing.T) { + mt1 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} + mt2 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + common := commonMediaTypes(mt1, mt2) + assert.Equal(t, len(common), 1) + assert.Equal(t, common[0], pbs.MEDIA_TYPE_BANNER) + + common2 := commonMediaTypes(mt2, mt1) + assert.Equal(t, len(common2), 1) + assert.Equal(t, common2[0], pbs.MEDIA_TYPE_BANNER) + + mt3 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + mt4 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + common3 := commonMediaTypes(mt3, mt4) + assert.Equal(t, len(common3), 2) + + mt5 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} + mt6 := []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO} + common4 := commonMediaTypes(mt5, mt6) + assert.Equal(t, len(common4), 0) +} + +func TestOpenRTB(t *testing.T) { + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + Instl: 1, + }, + }, + } + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) + + assert.Equal(t, err, nil) + assert.Equal(t, resp.Imp[0].ID, "unitCode") + assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) + assert.EqualValues(t, *resp.Imp[0].Banner.H, 12) + assert.EqualValues(t, resp.Imp[0].Instl, 1) + + assert.Nil(t, resp.User.Ext) + assert.Nil(t, resp.Regs) +} + +func TestOpenRTBVideo(t *testing.T) { + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + Video: pbs.PBSVideo{ + Mimes: []string{"video/mp4"}, + Minduration: 15, + Maxduration: 30, + Startdelay: 5, + Skippable: 0, + PlaybackMethod: 1, + }, + }, + }, + } + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}) + + assert.Equal(t, err, nil) + assert.Equal(t, resp.Imp[0].ID, "unitCode") + assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) + assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) + assert.EqualValues(t, *resp.Imp[0].Video.StartDelay, openrtb2.StartDelay(5)) + assert.EqualValues(t, resp.Imp[0].Video.PlaybackMethod, []openrtb2.PlaybackMethod{openrtb2.PlaybackMethod(1)}) + assert.EqualValues(t, resp.Imp[0].Video.MIMEs, []string{"video/mp4"}) +} + +func TestOpenRTBVideoNoVideoData(t *testing.T) { + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + }, + }, + } + _, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}) + + assert.NotEqual(t, err, nil) + +} + +func TestOpenRTBVideoFilteredOut(t *testing.T) { + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + Video: pbs.PBSVideo{ + Mimes: []string{"video/mp4"}, + Minduration: 15, + Maxduration: 30, + Startdelay: 5, + Skippable: 0, + PlaybackMethod: 1, + }, + }, + { + Code: "unitCode2", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + }, + }, + } + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) + assert.Equal(t, err, nil) + for i := 0; i < len(resp.Imp); i++ { + if resp.Imp[i].Video != nil { + t.Errorf("No video impressions should exist.") + } + } +} + +func TestOpenRTBMultiMediaImp(t *testing.T) { + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + Video: pbs.PBSVideo{ + Mimes: []string{"video/mp4"}, + Minduration: 15, + Maxduration: 30, + Startdelay: 5, + Skippable: 0, + PlaybackMethod: 1, + }, + }, + }, + } + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}) + assert.Equal(t, err, nil) + assert.Equal(t, len(resp.Imp), 1) + assert.Equal(t, resp.Imp[0].ID, "unitCode") + assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) + assert.EqualValues(t, resp.Imp[0].Video.W, 10) + assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) + assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) +} + +func TestOpenRTBMultiMediaImpFiltered(t *testing.T) { + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + Video: pbs.PBSVideo{ + Mimes: []string{"video/mp4"}, + Minduration: 15, + Maxduration: 30, + Startdelay: 5, + Skippable: 0, + PlaybackMethod: 1, + }, + }, + }, + } + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) + assert.Equal(t, err, nil) + assert.Equal(t, len(resp.Imp), 1) + assert.Equal(t, resp.Imp[0].ID, "unitCode") + assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) + assert.EqualValues(t, resp.Imp[0].Video, (*openrtb2.Video)(nil)) +} + +func TestOpenRTBNoSize(t *testing.T) { + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + }, + }, + } + _, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) + if err == nil { + t.Errorf("Bids without impressions should not be allowed.") + } +} + +func TestOpenRTBMobile(t *testing.T) { + pbReq := pbs.PBSRequest{ + AccountID: "test_account_id", + Tid: "test_tid", + CacheMarkup: 1, + SortBids: 1, + MaxKeyLength: 20, + Secure: 1, + TimeoutMillis: 1000, + App: &openrtb2.App{ + Bundle: "AppNexus.PrebidMobileDemo", + Publisher: &openrtb2.Publisher{ + ID: "1995257847363113", + }, + }, + Device: &openrtb2.Device{ + UA: "test_ua", + IP: "test_ip", + Make: "test_make", + Model: "test_model", + IFA: "test_ifa", + }, + User: &openrtb2.User{ + BuyerUID: "test_buyeruid", + }, + } + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 300, + H: 250, + }, + }, + }, + }, + } + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) + assert.Equal(t, err, nil) + assert.Equal(t, resp.Imp[0].ID, "unitCode") + assert.EqualValues(t, *resp.Imp[0].Banner.W, 300) + assert.EqualValues(t, *resp.Imp[0].Banner.H, 250) + + assert.EqualValues(t, resp.App.Bundle, "AppNexus.PrebidMobileDemo") + assert.EqualValues(t, resp.App.Publisher.ID, "1995257847363113") + assert.EqualValues(t, resp.User.BuyerUID, "test_buyeruid") + + assert.EqualValues(t, resp.Device.UA, "test_ua") + assert.EqualValues(t, resp.Device.IP, "test_ip") + assert.EqualValues(t, resp.Device.Make, "test_make") + assert.EqualValues(t, resp.Device.Model, "test_model") + assert.EqualValues(t, resp.Device.IFA, "test_ifa") +} + +func TestOpenRTBEmptyUser(t *testing.T) { + pbReq := pbs.PBSRequest{ + User: &openrtb2.User{}, + } + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode2", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + }, + }, + } + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) + assert.Equal(t, err, nil) + assert.EqualValues(t, resp.User, &openrtb2.User{}) +} + +func TestOpenRTBUserWithCookie(t *testing.T) { + pbsCookie := usersync.NewCookie() + pbsCookie.TrySync("test", "abcde") + pbReq := pbs.PBSRequest{ + User: &openrtb2.User{}, + } + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 300, + H: 250, + }, + }, + }, + }, + } + pbReq.Cookie = pbsCookie + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) + assert.Equal(t, err, nil) + assert.EqualValues(t, resp.User.BuyerUID, "abcde") +} + +func TestSizesCopy(t *testing.T) { + formats := []openrtb2.Format{ + { + W: 10, + }, + { + Ext: []byte{0x5}, + }, + } + clone := copyFormats(formats) + + if len(clone) != 2 { + t.Error("The copy should have 2 elements") + } + if clone[0].W != 10 { + t.Error("The Format's width should be preserved.") + } + if len(clone[1].Ext) != 1 || clone[1].Ext[0] != 0x5 { + t.Error("The Format's Ext should be preserved.") + } + if &formats[0] == &clone[0] || &formats[1] == &clone[1] { + t.Error("The Format elements should not point to the same instance") + } + if &formats[0] == &clone[0] || &formats[1] == &clone[1] { + t.Error("The Format elements should not point to the same instance") + } + if &formats[1].Ext[0] == &clone[1].Ext[0] { + t.Error("The Format.Ext property should point to two different instances") + } +} + +func TestMakeVideo(t *testing.T) { + adUnit := pbs.PBSAdUnit{ + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + Video: pbs.PBSVideo{ + Mimes: []string{"video/mp4"}, + Minduration: 15, + Maxduration: 30, + Startdelay: 5, + Skippable: 0, + PlaybackMethod: 1, + Protocols: []int8{1, 2, 5, 6}, + }, + } + video := makeVideo(adUnit) + assert.EqualValues(t, video.MinDuration, 15) + assert.EqualValues(t, video.MaxDuration, 30) + assert.EqualValues(t, *video.StartDelay, openrtb2.StartDelay(5)) + assert.EqualValues(t, len(video.PlaybackMethod), 1) + assert.EqualValues(t, len(video.Protocols), 4) +} + +func TestGDPR(t *testing.T) { + + rawUserExt := json.RawMessage(`{"consent": "12345"}`) + userExt, _ := json.Marshal(rawUserExt) + + rawRegsExt := json.RawMessage(`{"gdpr": 1}`) + regsExt, _ := json.Marshal(rawRegsExt) + + pbReq := pbs.PBSRequest{ + User: &openrtb2.User{ + Ext: userExt, + }, + Regs: &openrtb2.Regs{ + Ext: regsExt, + }, + } + + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + Instl: 1, + }, + }, + } + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) + + assert.Equal(t, err, nil) + assert.Equal(t, resp.Imp[0].ID, "unitCode") + assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) + assert.EqualValues(t, *resp.Imp[0].Banner.H, 12) + assert.EqualValues(t, resp.Imp[0].Instl, 1) + + assert.EqualValues(t, resp.User.Ext, userExt) + assert.EqualValues(t, resp.Regs.Ext, regsExt) +} + +func TestGDPRMobile(t *testing.T) { + rawUserExt := json.RawMessage(`{"consent": "12345"}`) + userExt, _ := json.Marshal(rawUserExt) + + rawRegsExt := json.RawMessage(`{"gdpr": 1}`) + regsExt, _ := json.Marshal(rawRegsExt) + + pbReq := pbs.PBSRequest{ + AccountID: "test_account_id", + Tid: "test_tid", + CacheMarkup: 1, + SortBids: 1, + MaxKeyLength: 20, + Secure: 1, + TimeoutMillis: 1000, + App: &openrtb2.App{ + Bundle: "AppNexus.PrebidMobileDemo", + Publisher: &openrtb2.Publisher{ + ID: "1995257847363113", + }, + }, + Device: &openrtb2.Device{ + UA: "test_ua", + IP: "test_ip", + Make: "test_make", + Model: "test_model", + IFA: "test_ifa", + }, + User: &openrtb2.User{ + BuyerUID: "test_buyeruid", + Ext: userExt, + }, + Regs: &openrtb2.Regs{ + Ext: regsExt, + }, + } + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 300, + H: 250, + }, + }, + }, + }, + } + resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) + assert.Equal(t, err, nil) + assert.Equal(t, resp.Imp[0].ID, "unitCode") + assert.EqualValues(t, *resp.Imp[0].Banner.W, 300) + assert.EqualValues(t, *resp.Imp[0].Banner.H, 250) + + assert.EqualValues(t, resp.App.Bundle, "AppNexus.PrebidMobileDemo") + assert.EqualValues(t, resp.App.Publisher.ID, "1995257847363113") + assert.EqualValues(t, resp.User.BuyerUID, "test_buyeruid") + + assert.EqualValues(t, resp.Device.UA, "test_ua") + assert.EqualValues(t, resp.Device.IP, "test_ip") + assert.EqualValues(t, resp.Device.Make, "test_make") + assert.EqualValues(t, resp.Device.Model, "test_model") + assert.EqualValues(t, resp.Device.IFA, "test_ifa") + + assert.EqualValues(t, resp.User.Ext, userExt) + assert.EqualValues(t, resp.Regs.Ext, regsExt) +} diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 19024f4a123..c2e9fffa0fe 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -1,8 +1,11 @@ package pubmatic import ( + "bytes" + "context" "encoding/json" "fmt" + "io/ioutil" "net/http" "strconv" "strings" @@ -13,23 +16,46 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "golang.org/x/net/context/ctxhttp" ) const MAX_IMPRESSIONS_PUBMATIC = 30 type PubmaticAdapter struct { - URI string + http *adapters.HTTPAdapter + URI string } -type pubmaticBidExt struct { - BidType *int `json:"BidType,omitempty"` - VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` +// used for cookies and such +func (a *PubmaticAdapter) Name() string { + return "pubmatic" +} + +func (a *PubmaticAdapter) SkipNoCookies() bool { + return false +} + +// Below is bidder specific parameters for pubmatic adaptor, +// PublisherId and adSlot are mandatory parameters, others are optional parameters +// Keywords is bid specific parameter, +// WrapExt needs to be sent once per bid request +type pubmaticParams struct { + PublisherId string `json:"publisherId"` + AdSlot string `json:"adSlot"` + WrapExt json.RawMessage `json:"wrapper,omitempty"` + Keywords map[string]string `json:"keywords,omitempty"` } type pubmaticBidExtVideo struct { Duration *int `json:"duration,omitempty"` } +type pubmaticBidExt struct { + BidType *int `json:"BidType,omitempty"` + VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` +} + type ExtImpBidderPubmatic struct { adapters.ExtImpBidder Data *ExtData `json:"data,omitempty"` @@ -46,6 +72,16 @@ type ExtAdServer struct { } const ( + INVALID_PARAMS = "Invalid BidParam" + MISSING_PUBID = "Missing PubID" + MISSING_ADSLOT = "Missing AdSlot" + INVALID_WRAPEXT = "Invalid WrapperExt" + INVALID_ADSIZE = "Invalid AdSize" + INVALID_WIDTH = "Invalid Width" + INVALID_HEIGHT = "Invalid Height" + INVALID_MEDIATYPE = "Invalid MediaType" + INVALID_ADSLOT = "Invalid AdSlot" + dctrKeyName = "key_val" pmZoneIDKeyName = "pmZoneId" pmZoneIDKeyNameOld = "pmZoneID" @@ -53,6 +89,251 @@ const ( AdServerGAM = "gam" ) +func PrepareLogMessage(tID, pubId, adUnitId, bidID, details string, args ...interface{}) string { + return fmt.Sprintf("[PUBMATIC] ReqID [%s] PubID [%s] AdUnit [%s] BidID [%s] %s \n", + tID, pubId, adUnitId, bidID, details) +} + +func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + pbReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) + + if err != nil { + logf("[PUBMATIC] Failed to make ortb request for request id [%s] \n", pbReq.ID) + return nil, err + } + + var errState []string + adSlotFlag := false + pubId := "" + wrapExt := "" + if len(bidder.AdUnits) > MAX_IMPRESSIONS_PUBMATIC { + logf("[PUBMATIC] First %d impressions will be considered from request tid %s\n", + MAX_IMPRESSIONS_PUBMATIC, pbReq.ID) + } + + for i, unit := range bidder.AdUnits { + var params pubmaticParams + err := json.Unmarshal(unit.Params, ¶ms) + if err != nil { + errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_PARAMS, unit.Params)) + logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, + fmt.Sprintf("Ignored bid: invalid JSON [%s] err [%s]", unit.Params, err.Error()))) + continue + } + + if params.PublisherId == "" { + errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, MISSING_PUBID, unit.Params)) + logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, + fmt.Sprintf("Ignored bid: Publisher Id missing"))) + continue + } + pubId = params.PublisherId + + if params.AdSlot == "" { + errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, MISSING_ADSLOT, unit.Params)) + logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, + fmt.Sprintf("Ignored bid: adSlot missing"))) + continue + } + + // Parse Wrapper Extension i.e. ProfileID and VersionID only once per request + if wrapExt == "" && len(params.WrapExt) != 0 { + var wrapExtMap map[string]int + err := json.Unmarshal([]byte(params.WrapExt), &wrapExtMap) + if err != nil { + errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_WRAPEXT, unit.Params)) + logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, + fmt.Sprintf("Ignored bid: Wrapper Extension Invalid"))) + continue + } + wrapExt = string(params.WrapExt) + } + + adSlotStr := strings.TrimSpace(params.AdSlot) + adSlot := strings.Split(adSlotStr, "@") + if len(adSlot) == 2 && adSlot[0] != "" && adSlot[1] != "" { + // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply + if len(pbReq.Imp) <= i { + break + } + if pbReq.Imp[i].Banner != nil { + adSize := strings.Split(strings.ToLower(strings.TrimSpace(adSlot[1])), "x") + if len(adSize) == 2 { + width, err := strconv.Atoi(strings.TrimSpace(adSize[0])) + if err != nil { + errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_WIDTH, unit.Params)) + logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, + fmt.Sprintf("Ignored bid: invalid adSlot width [%s]", adSize[0]))) + continue + } + + heightStr := strings.Split(strings.TrimSpace(adSize[1]), ":") + height, err := strconv.Atoi(strings.TrimSpace(heightStr[0])) + if err != nil { + errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_HEIGHT, unit.Params)) + logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, + fmt.Sprintf("Ignored bid: invalid adSlot height [%s]", heightStr[0]))) + continue + } + + pbReq.Imp[i].TagID = strings.TrimSpace(adSlot[0]) + 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) + pbReq.Imp[i].Ext = json.RawMessage([]byte(kvstr)) + } else { + pbReq.Imp[i].Ext = nil + } + + adSlotFlag = true + } else { + errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_ADSIZE, unit.Params)) + logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, + fmt.Sprintf("Ignored bid: invalid adSize [%s]", adSize))) + continue + } + } else { + errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_MEDIATYPE, unit.Params)) + logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, + fmt.Sprintf("Ignored bid: invalid Media Type"))) + continue + } + } else { + errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_ADSLOT, unit.Params)) + logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, + fmt.Sprintf("Ignored bid: invalid adSlot [%s]", params.AdSlot))) + continue + } + + if pbReq.Site != nil { + siteCopy := *pbReq.Site + siteCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} + pbReq.Site = &siteCopy + } + if pbReq.App != nil { + appCopy := *pbReq.App + appCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} + pbReq.App = &appCopy + } + } + + if !(adSlotFlag) { + return nil, &errortypes.BadInput{ + Message: "Incorrect adSlot / Publisher params, Error list: [" + strings.Join(errState, ",") + "]", + } + } + + if wrapExt != "" { + rawExt := fmt.Sprintf("{\"wrapper\": %s}", wrapExt) + pbReq.Ext = json.RawMessage(rawExt) + } + + reqJSON, err := json.Marshal(pbReq) + + debug := &pbs.BidderDebug{ + RequestURI: a.URI, + } + + if req.IsDebug { + debug.RequestBody = string(reqJSON) + bidder.Debug = append(bidder.Debug, debug) + } + + userId, _, _ := req.Cookie.GetUID(a.Name()) + httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON)) + httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") + httpReq.Header.Add("Accept", "application/json") + httpReq.AddCookie(&http.Cookie{ + Name: "KADUSERCOOKIE", + Value: userId, + }) + + pbResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) + if err != nil { + return nil, err + } + + debug.StatusCode = pbResp.StatusCode + + if pbResp.StatusCode == http.StatusNoContent { + return nil, nil + } + + if pbResp.StatusCode == http.StatusBadRequest { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), + } + } + + if pbResp.StatusCode != http.StatusOK { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), + } + } + + defer pbResp.Body.Close() + body, err := ioutil.ReadAll(pbResp.Body) + if err != nil { + return nil, err + } + + if req.IsDebug { + debug.ResponseBody = string(body) + } + + var bidResp openrtb2.BidResponse + err = json.Unmarshal(body, &bidResp) + if err != nil { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), + } + } + + bids := make(pbs.PBSBidSlice, 0) + + numBids := 0 + for _, sb := range bidResp.SeatBid { + for _, bid := range sb.Bid { + numBids++ + + bidID := bidder.LookupBidID(bid.ImpID) + if bidID == "" { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), + } + } + + pbid := pbs.PBSBid{ + BidID: bidID, + AdUnitCode: bid.ImpID, + BidderCode: bidder.BidderCode, + Price: bid.Price, + Adm: bid.AdM, + Creative_id: bid.CrID, + Width: bid.W, + Height: bid.H, + DealId: bid.DealID, + } + + var bidExt pubmaticBidExt + mediaType := openrtb_ext.BidTypeBanner + if err := json.Unmarshal(bid.Ext, &bidExt); err == nil { + mediaType = getBidType(&bidExt) + } + pbid.CreativeMediaType = string(mediaType) + + bids = append(bids, &pbid) + logf("[PUBMATIC] Returned Bid for PubID [%s] AdUnit [%s] BidID [%s] Size [%dx%d] Price [%f] \n", + pubId, pbid.AdUnitCode, pbid.BidID, pbid.Width, pbid.Height, pbid.Price) + } + } + + return bids, nil +} + func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) @@ -254,6 +535,7 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er } return nil + } func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) { @@ -271,6 +553,22 @@ func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[s } } +func prepareImpressionExt(keywords map[string]string) string { + + eachKv := make([]string, 0, len(keywords)) + for key, val := range keywords { + if len(val) == 0 { + logf("No values present for key = %s", key) + continue + } else { + eachKv = append(eachKv, fmt.Sprintf("\"%s\":\"%s\"", key, val)) + } + } + + kvStr := "{" + strings.Join(eachKv, ",") + "}" + return kvStr +} + func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -348,6 +646,15 @@ func logf(msg string, args ...interface{}) { } } +func NewPubmaticLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PubmaticAdapter { + a := adapters.NewHTTPAdapter(config) + + return &PubmaticAdapter{ + http: a, + URI: uri, + } +} + // Builder builds a new instance of the Pubmatic adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &PubmaticAdapter{ diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index ac7dbdb711f..2e8a6804850 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -1,11 +1,25 @@ package pubmatic import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "net/http/httptest" "testing" + "time" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" ) func TestJsonSamples(t *testing.T) { @@ -19,8 +33,655 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "pubmatictest", bidder) } +// ---------------------------------------------------------------------------- +// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we +// clean up the existing code and make everything openrtb2. + +func CompareStringValue(val1 string, val2 string, t *testing.T) { + if val1 != val2 { + t.Fatalf(fmt.Sprintf("Expected = %s , Actual = %s", val2, val1)) + } +} + +func DummyPubMaticServer(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 + } + + resp := openrtb2.BidResponse{ + ID: breq.ID, + BidID: "bidResponse_ID", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "pubmatic", + Bid: make([]openrtb2.Bid, 0), + }, + }, + } + rand.Seed(int64(time.Now().UnixNano())) + var bids []openrtb2.Bid + + for i, imp := range breq.Imp { + bids = append(bids, openrtb2.Bid{ + ID: fmt.Sprintf("SeatID_%d", i), + ImpID: imp.ID, + Price: float64(int(rand.Float64()*1000)) / 100, + AdID: fmt.Sprintf("adID-%d", i), + AdM: "AdContent", + CrID: fmt.Sprintf("creative-%d", i), + W: *imp.Banner.W, + H: *imp.Banner.H, + DealID: fmt.Sprintf("DealID_%d", i), + }) + } + resp.SeatBid[0].Bid = bids + + 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 TestPubmaticInvalidCall(t *testing.T) { + + an := NewPubmaticLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "blah") + + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{} + _, err := an.Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("No error received for invalid request") + } +} + +func TestPubmaticTimeout(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + <-time.After(2 * time.Millisecond) + }), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + ctx, cancel := context.WithTimeout(context.Background(), 0) + defer cancel() + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 120, + H: 240, + }, + }, + Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), + }, + }, + } + _, err := an.Call(ctx, &pbReq, &pbBidder) + if err == nil || err != context.DeadlineExceeded { + t.Fatalf("No timeout received for timed out request: %v", err) + } +} + +func TestPubmaticInvalidJson(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Blah") + }), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 120, + H: 240, + }, + }, + Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), + }, + }, + } + _, err := an.Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("No error received for invalid request") + } +} + +func TestPubmaticInvalidStatusCode(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Send 404 + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + }), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 120, + H: 240, + }, + }, + Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), + }, + }, + } + _, err := an.Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("No error received for invalid request") + } +} + +func TestPubmaticInvalidInputParameters(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + ctx := context.Background() + + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + BidID: "bidid", + Sizes: []openrtb2.Format{ + { + W: 120, + H: 240, + }, + }, + }, + }, + } + + pbReq.IsDebug = true + inValidPubmaticParams := []json.RawMessage{ + // Invalid Request JSON + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\""), + // Missing adSlot in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\"}"), + // Missing publisher ID + json.RawMessage("{\"adSlot\": \"slot@120x240\"}"), + // Missing slot name in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"@120x240\"}"), + // Invalid adSize in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120-240\"}"), + // Missing impression width and height in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@\"}"), + // Missing height in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120\"}"), + // Missing width in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@x120\"}"), + // Incorrect width param in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@valx120\"}"), + // Incorrect height param in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120xval\"}"), + // Empty slot name in AdUnits.Params, + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" @120x240\"}"), + // Empty width in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@ x240\"}"), + // Empty height in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x \"}"), + // Empty height in AdUnits.Params + json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" @120x \"}"), + // Invalid Keywords + json.RawMessage(`{"publisherId": "640", "adSlot": "slot1@336x280","keywords":{"pmZoneId":1},"wrapper":{"version":2,"profile":595}}`), + // Invalid Wrapper ext + json.RawMessage(`{"publisherId": "640", "adSlot": "slot1@336x280","keywords":{"pmZoneId":"Zone1,Zone2"},"wrapper":{"version":"2","profile":595}}`), + } + + for _, param := range inValidPubmaticParams { + pbBidder.AdUnits[0].Params = param + _, err := an.Call(ctx, &pbReq, &pbBidder) + if err == nil { + t.Fatalf("Should get errors for params = %v", string(param)) + } + } + +} + +func TestPubmaticBasicResponse_MandatoryParams(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + BidID: "bidid", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 336, + H: 280, + }, + }, + Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), + }, + }, + } + pbReq.IsDebug = true + bids, err := an.Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + if len(bids) != 1 { + t.Fatalf("Should have received one bid") + } +} + +func TestPubmaticBasicResponse_AllParams(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + BidID: "bidid", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 336, + H: 280, + }, + }, + Params: json.RawMessage(`{"publisherId": "640", + "adSlot": "slot1@336x280", + "keywords":{ + "pmZoneId": "Zone1,Zone2" + }, + "wrapper": + {"version":2, + "profile":595} + }`), + }, + }, + } + pbReq.IsDebug = true + bids, err := an.Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + if len(bids) != 1 { + t.Fatalf("Should have received one bid") + } +} + +func TestPubmaticMultiImpressionResponse(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode1", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + BidID: "bidid", + Sizes: []openrtb2.Format{ + { + W: 336, + H: 280, + }, + }, + Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), + }, + { + Code: "unitCode1", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + BidID: "bidid", + Sizes: []openrtb2.Format{ + { + W: 800, + H: 200, + }, + }, + Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@800x200\"}"), + }, + }, + } + bids, err := an.Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + if len(bids) != 2 { + t.Fatalf("Should have received two bids") + } +} + +func TestPubmaticMultiAdUnitResponse(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode1", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + BidID: "bidid", + Sizes: []openrtb2.Format{ + { + W: 336, + H: 280, + }, + }, + Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), + }, + { + Code: "unitCode2", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + BidID: "bidid", + Sizes: []openrtb2.Format{ + { + W: 800, + H: 200, + }, + }, + Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@800x200\"}"), + }, + }, + } + bids, err := an.Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + if len(bids) != 2 { + t.Fatalf("Should have received one bid") + } + +} + +func TestPubmaticMobileResponse(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + BidID: "bidid", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 336, + H: 280, + }, + }, + Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), + }, + }, + } + + pbReq.App = &openrtb2.App{ + ID: "com.test", + Name: "testApp", + } + + bids, err := an.Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + if len(bids) != 1 { + t.Fatalf("Should have received one bid") + } +} +func TestPubmaticInvalidLookupBidIDParameter(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 120, + H: 240, + }, + }, + }, + }, + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}") + _, err := an.Call(ctx, &pbReq, &pbBidder) + + CompareStringValue(err.Error(), "Unknown ad unit code 'unitCode'", t) +} + +func TestPubmaticAdSlotParams(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewPubmaticLegacyAdapter(&conf, server.URL) + + ctx := context.Background() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + { + Code: "unitCode", + BidID: "bidid", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + Sizes: []openrtb2.Format{ + { + W: 120, + H: 240, + }, + }, + }, + }, + } + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" slot@120x240\"}") + bids, err := an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot @120x240\"}") + bids, err = an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240 \"}") + bids, err = an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@ 120x240\"}") + bids, err = an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@220 x240\"}") + bids, err = an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x 240\"}") + bids, err = an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240:1\"}") + bids, err = an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x 240:1\"}") + bids, err = an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240 :1\"}") + bids, err = an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } + + pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240: 1\"}") + bids, err = an.Call(ctx, &pbReq, &pbBidder) + if err != nil && len(bids) != 1 { + t.Fatalf("Should not return err") + } +} + +func TestPubmaticSampleRequest(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) + defer server.Close() + + pbReq := pbs.PBSRequest{ + AdUnits: make([]pbs.AdUnit, 1), + } + pbReq.AdUnits[0] = pbs.AdUnit{ + Code: "adUnit_1", + Sizes: []openrtb2.Format{ + { + W: 100, + H: 120, + }, + }, + Bids: []pbs.Bids{ + { + BidderCode: "pubmatic", + BidID: "BidID", + Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@100x120\"}"), + }, + }, + } + + pbReq.IsDebug = true + + body := new(bytes.Buffer) + err := json.NewEncoder(body).Encode(pbReq) + if err != nil { + t.Fatalf("Error when serializing request") + } + + httpReq := httptest.NewRequest("POST", server.URL, body) + httpReq.Header.Add("Referer", "http://test.com/sports") + pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) + pc.TrySync("pubmatic", "12345") + fakewriter := httptest.NewRecorder() + + pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) + httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) + + cacheClient, _ := dummycache.New() + hcs := config.HostCookie{} + + _, err = pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, cacheClient, &hcs) + if err != nil { + t.Fatalf("Error when parsing request: %v", err) + } +} + func TestGetBidTypeVideo(t *testing.T) { - pubmaticExt := &pubmaticBidExt{} + pubmaticExt := new(pubmaticBidExt) pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 1 actualBidTypeValue := getBidType(pubmaticExt) @@ -30,8 +691,8 @@ func TestGetBidTypeVideo(t *testing.T) { } func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { - pubmaticExt := &pubmaticBidExt{} - actualBidTypeValue := getBidType(pubmaticExt) + pubmaticExt := pubmaticBidExt{} + actualBidTypeValue := getBidType(&pubmaticExt) // banner is the default bid type when no bidType key is present in the bid.ext if actualBidTypeValue != "banner" { t.Errorf("Expected Bid Type value was: banner, actual value is: %v", actualBidTypeValue) @@ -39,7 +700,7 @@ func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { } func TestGetBidTypeBanner(t *testing.T) { - pubmaticExt := &pubmaticBidExt{} + pubmaticExt := new(pubmaticBidExt) pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 0 actualBidTypeValue := getBidType(pubmaticExt) @@ -49,7 +710,7 @@ func TestGetBidTypeBanner(t *testing.T) { } func TestGetBidTypeNative(t *testing.T) { - pubmaticExt := &pubmaticBidExt{} + pubmaticExt := new(pubmaticBidExt) pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 2 actualBidTypeValue := getBidType(pubmaticExt) @@ -59,7 +720,7 @@ func TestGetBidTypeNative(t *testing.T) { } func TestGetBidTypeForUnsupportedCode(t *testing.T) { - pubmaticExt := &pubmaticBidExt{} + pubmaticExt := new(pubmaticBidExt) pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 99 actualBidTypeValue := getBidType(pubmaticExt) diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index f756d5dd31a..6b6b4305607 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -1,21 +1,27 @@ package pulsepoint import ( + "bytes" + "context" "encoding/json" "fmt" + "io/ioutil" "net/http" "strconv" + "strings" + "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" - - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/pbs" + "golang.org/x/net/context/ctxhttp" ) type PulsePointAdapter struct { - URI string + http *adapters.HTTPAdapter + URI string } // Builds an instance of PulsePointAdapter @@ -162,3 +168,192 @@ func getBidType(imp openrtb2.Imp) openrtb_ext.BidType { } return "" } + +///////////////////////////////// +// Legacy implementation: Start +///////////////////////////////// + +func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter { + a := adapters.NewHTTPAdapter(config) + + return &PulsePointAdapter{ + http: a, + URI: uri, + } +} + +// used for cookies and such +func (a *PulsePointAdapter) Name() string { + return "pulsepoint" +} + +// parameters for pulsepoint adapter. +type PulsepointParams struct { + PublisherId int `json:"cp"` + TagId int `json:"ct"` + AdSize string `json:"cf"` +} + +func (a *PulsePointAdapter) SkipNoCookies() bool { + return false +} + +func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} + ppReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) + + if err != nil { + return nil, err + } + + for i, unit := range bidder.AdUnits { + var params PulsepointParams + err := json.Unmarshal(unit.Params, ¶ms) + if err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + if params.PublisherId == 0 { + return nil, &errortypes.BadInput{ + Message: "Missing PublisherId param cp", + } + } + if params.TagId == 0 { + return nil, &errortypes.BadInput{ + Message: "Missing TagId param ct", + } + } + if params.AdSize == "" { + return nil, &errortypes.BadInput{ + Message: "Missing AdSize param cf", + } + } + // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply + if len(ppReq.Imp) <= i { + break + } + ppReq.Imp[i].TagID = strconv.Itoa(params.TagId) + publisher := &openrtb2.Publisher{ID: strconv.Itoa(params.PublisherId)} + if ppReq.Site != nil { + siteCopy := *ppReq.Site + siteCopy.Publisher = publisher + ppReq.Site = &siteCopy + } else { + appCopy := *ppReq.App + appCopy.Publisher = publisher + ppReq.App = &appCopy + } + if ppReq.Imp[i].Banner != nil { + var size = strings.Split(strings.ToLower(params.AdSize), "x") + if len(size) == 2 { + width, err := strconv.Atoi(size[0]) + if err == nil { + ppReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) + } else { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Invalid Width param %s", size[0]), + } + } + height, err := strconv.Atoi(size[1]) + if err == nil { + ppReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) + } else { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Invalid Height param %s", size[1]), + } + } + } else { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Invalid AdSize param %s", params.AdSize), + } + } + } + } + reqJSON, err := json.Marshal(ppReq) + debug := &pbs.BidderDebug{ + RequestURI: a.URI, + } + + if req.IsDebug { + debug.RequestBody = string(reqJSON) + bidder.Debug = append(bidder.Debug, debug) + } + + httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON)) + httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") + httpReq.Header.Add("Accept", "application/json") + + ppResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) + if err != nil { + return nil, err + } + + debug.StatusCode = ppResp.StatusCode + + if ppResp.StatusCode == http.StatusNoContent { + return nil, nil + } + + if ppResp.StatusCode == http.StatusBadRequest { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status: %d", ppResp.StatusCode), + } + } + + if ppResp.StatusCode != http.StatusOK { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status: %d", ppResp.StatusCode), + } + } + + defer ppResp.Body.Close() + body, err := ioutil.ReadAll(ppResp.Body) + if err != nil { + return nil, err + } + + if req.IsDebug { + debug.ResponseBody = string(body) + } + + var bidResp openrtb2.BidResponse + err = json.Unmarshal(body, &bidResp) + if err != nil { + return nil, &errortypes.BadServerResponse{ + Message: err.Error(), + } + } + + bids := make(pbs.PBSBidSlice, 0) + + for _, sb := range bidResp.SeatBid { + for _, bid := range sb.Bid { + bidID := bidder.LookupBidID(bid.ImpID) + if bidID == "" { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), + } + } + + pbid := pbs.PBSBid{ + BidID: bidID, + AdUnitCode: bid.ImpID, + BidderCode: bidder.BidderCode, + Price: bid.Price, + Adm: bid.AdM, + Creative_id: bid.CrID, + Width: bid.W, + Height: bid.H, + CreativeMediaType: string(openrtb_ext.BidTypeBanner), + } + bids = append(bids, &pbid) + } + } + + return bids, nil +} + +///////////////////////////////// +// Legacy implementation: End +///////////////////////////////// diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index 8929898522a..a4e20b04859 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -1,11 +1,26 @@ package pulsepoint import ( + "encoding/json" + "net/http" "testing" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" + + "bytes" + "context" + "fmt" + "io/ioutil" + "net/http/httptest" + "time" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" ) func TestJsonSamples(t *testing.T) { @@ -18,3 +33,280 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "pulsepointtest", bidder) } + +///////////////////////////////// +// Legacy implementation: Start +///////////////////////////////// + +/** + * Verify adapter names are setup correctly. + */ +func TestPulsePointAdapterNames(t *testing.T) { + adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://localhost/bid") + adapterstest.VerifyStringValue(adapter.Name(), "pulsepoint", t) +} + +/** + * Test required parameters not sent + */ +func TestPulsePointRequiredBidParameters(t *testing.T) { + adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://localhost/bid") + ctx := context.TODO() + req := SampleRequest(1, t) + bidder := req.Bidders[0] + // remove "ct" param and verify error message. + bidder.AdUnits[0].Params = json.RawMessage("{\"cp\": 2001, \"cf\": \"728X90\"}") + _, errTag := adapter.Call(ctx, req, bidder) + adapterstest.VerifyStringValue(errTag.Error(), "Missing TagId param ct", t) + // remove "cp" param and verify error message. + bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cf\": \"728X90\"}") + _, errPub := adapter.Call(ctx, req, bidder) + adapterstest.VerifyStringValue(errPub.Error(), "Missing PublisherId param cp", t) + // remove "cf" param and verify error message. + bidder.AdUnits[0].Params = json.RawMessage("{\"cp\": 2001, \"ct\": 1001}") + _, errSize := adapter.Call(ctx, req, bidder) + adapterstest.VerifyStringValue(errSize.Error(), "Missing AdSize param cf", t) + // invalid width parameter value for cf + bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"aXb\"}") + _, errWidth := adapter.Call(ctx, req, bidder) + adapterstest.VerifyStringValue(errWidth.Error(), "Invalid Width param a", t) + // invalid parameter values for cf + bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"12Xb\"}") + _, errHeight := adapter.Call(ctx, req, bidder) + adapterstest.VerifyStringValue(errHeight.Error(), "Invalid Height param b", t) + // invalid parameter values for cf + bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"12-20\"}") + _, errAdSizeValue := adapter.Call(ctx, req, bidder) + adapterstest.VerifyStringValue(errAdSizeValue.Error(), "Invalid AdSize param 12-20", t) +} + +/** + * Verify the openrtb request sent to Pulsepoint endpoint. + * Ensure the ct, cp, cf params are transformed and sent alright. + */ +func TestPulsePointOpenRTBRequest(t *testing.T) { + service := CreateService(adapterstest.BidOnTags("")) + server := service.Server + ctx := context.TODO() + req := SampleRequest(1, t) + bidder := req.Bidders[0] + adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + adapter.Call(ctx, req, bidder) + adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) + adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "1001", t) + adapterstest.VerifyStringValue(service.LastBidRequest.Site.Publisher.ID, "2001", t) + adapterstest.VerifyBannerSize(service.LastBidRequest.Imp[0].Banner, 728, 90, t) +} + +/** + * Verify bidding behavior. + */ +func TestPulsePointBiddingBehavior(t *testing.T) { + // setup server endpoint to return bid. + server := CreateService(adapterstest.BidOnTags("1001")).Server + ctx := context.TODO() + req := SampleRequest(1, t) + bidder := req.Bidders[0] + adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + bids, _ := adapter.Call(ctx, req, bidder) + // number of bids should be 1 + adapterstest.VerifyIntValue(len(bids), 1, t) + adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) + adapterstest.VerifyStringValue(bids[0].BidderCode, "pulsepoint", t) + adapterstest.VerifyStringValue(bids[0].Adm, "
This is an Ad
", t) + adapterstest.VerifyStringValue(bids[0].Creative_id, "Cr-234", t) + adapterstest.VerifyIntValue(int(bids[0].Width), 728, t) + adapterstest.VerifyIntValue(int(bids[0].Height), 90, t) + adapterstest.VerifyIntValue(int(bids[0].Price*100), 210, t) + adapterstest.VerifyStringValue(bids[0].CreativeMediaType, string(openrtb_ext.BidTypeBanner), t) +} + +/** + * Verify bidding behavior on multiple impressions, some impressions make a bid + */ +func TestPulsePointMultiImpPartialBidding(t *testing.T) { + // setup server endpoint to return bid. + service := CreateService(adapterstest.BidOnTags("1001")) + server := service.Server + ctx := context.TODO() + req := SampleRequest(2, t) + bidder := req.Bidders[0] + adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + bids, _ := adapter.Call(ctx, req, bidder) + // two impressions sent. + // number of bids should be 1 + adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) + adapterstest.VerifyIntValue(len(bids), 1, t) + adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) +} + +/** + * Verify bidding behavior on multiple impressions, all impressions passed back. + */ +func TestPulsePointMultiImpPassback(t *testing.T) { + // setup server endpoint to return bid. + service := CreateService(adapterstest.BidOnTags("")) + server := service.Server + ctx := context.TODO() + req := SampleRequest(2, t) + bidder := req.Bidders[0] + adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + bids, _ := adapter.Call(ctx, req, bidder) + // two impressions sent. + // number of bids should be 1 + adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) + adapterstest.VerifyIntValue(len(bids), 0, t) +} + +/** + * Verify bidding behavior on multiple impressions, all impressions passed back. + */ +func TestPulsePointMultiImpAllBid(t *testing.T) { + // setup server endpoint to return bid. + service := CreateService(adapterstest.BidOnTags("1001,1002")) + server := service.Server + ctx := context.TODO() + req := SampleRequest(2, t) + bidder := req.Bidders[0] + adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + bids, _ := adapter.Call(ctx, req, bidder) + // two impressions sent. + // number of bids should be 1 + adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) + adapterstest.VerifyIntValue(len(bids), 2, t) + adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) + adapterstest.VerifyStringValue(bids[1].AdUnitCode, "div-adunit-2", t) +} + +/** + * Verify bidding behavior on mobile app requests + */ +func TestMobileAppRequest(t *testing.T) { + // setup server endpoint to return bid. + service := CreateService(adapterstest.BidOnTags("1001")) + server := service.Server + ctx := context.TODO() + req := SampleRequest(1, t) + req.App = &openrtb2.App{ + ID: "com.facebook.katana", + Name: "facebook", + } + bidder := req.Bidders[0] + adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + bids, _ := adapter.Call(ctx, req, bidder) + // one mobile app impression sent. + // verify appropriate fields are sent to pulsepoint endpoint. + adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) + adapterstest.VerifyStringValue(service.LastBidRequest.App.ID, "com.facebook.katana", t) + adapterstest.VerifyIntValue(len(bids), 1, t) + adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) +} + +/** + * Produces a sample PBSRequest, for the impressions given. + */ +func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { + // create a request object + req := pbs.PBSRequest{ + AdUnits: make([]pbs.AdUnit, 2), + } + req.AccountID = "1" + tagId := 1001 + for i := 0; i < numberOfImpressions; i++ { + req.AdUnits[i] = pbs.AdUnit{ + Code: fmt.Sprintf("div-adunit-%d", i+1), + Sizes: []openrtb2.Format{ + { + W: 10, + H: 12, + }, + }, + Bids: []pbs.Bids{ + { + BidderCode: "pulsepoint", + BidID: fmt.Sprintf("Bid-%d", i+1), + Params: json.RawMessage(fmt.Sprintf("{\"ct\": %d, \"cp\": 2001, \"cf\": \"728X90\"}", tagId+i)), + }, + }, + } + } + // serialize the request to json + body := new(bytes.Buffer) + err := json.NewEncoder(body).Encode(req) + if err != nil { + t.Fatalf("Error when serializing request") + } + // setup a http request + httpReq := httptest.NewRequest("POST", CreateService(adapterstest.BidOnTags("")).Server.URL, body) + httpReq.Header.Add("Referer", "http://news.pub/topnews") + pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) + pc.TrySync("pulsepoint", "pulsepointUser123") + fakewriter := httptest.NewRecorder() + + pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) + httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) + // parse the http request + cacheClient, _ := dummycache.New() + hcs := config.HostCookie{} + + parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, cacheClient, &hcs) + if err != nil { + t.Fatalf("Error when parsing request: %v", err) + } + return parsedReq +} + +/** + * Represents a mock ORTB endpoint of PulsePoint. Would return a bid + * for TagId 1001 and passback for 1002 as the default behavior. + */ +func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { + service := adapterstest.OrtbMockService{} + var lastBidRequest openrtb2.BidRequest + server := httptest.NewServer(http.HandlerFunc(func(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 + } + lastBidRequest = breq + var bids []openrtb2.Bid + for i, imp := range breq.Imp { + if tagsToBid[imp.TagID] { + bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) + } + } + // no bids were produced, pulsepoint service returns 204 + if len(bids) == 0 { + w.WriteHeader(204) + } else { + // serialize the bids to openrtb2.BidResponse + js, _ := json.Marshal(openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: bids, + }, + }, + }) + w.Header().Set("Content-Type", "application/json") + w.Write(js) + } + })) + service.Server = server + service.LastBidRequest = &lastBidRequest + return service +} + +///////////////////////////////// +// Legacy implementation: End +///////////////////////////////// diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index ace1bfaa12d..80c62df16a1 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -1,30 +1,53 @@ package rubicon import ( + "bytes" + "context" "encoding/json" "fmt" + "github.com/buger/jsonparser" + "io/ioutil" "net/http" "net/url" "strconv" "strings" + "github.com/golang/glog" + "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" - - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/pbs" + "golang.org/x/net/context/ctxhttp" ) const badvLimitSize = 50 type RubiconAdapter struct { + http *adapters.HTTPAdapter URI string XAPIUsername string XAPIPassword string } +func (a *RubiconAdapter) Name() string { + return "rubicon" +} + +func (a *RubiconAdapter) SkipNoCookies() bool { + return false +} + +type rubiconParams struct { + AccountId int `json:"accountId"` + SiteId int `json:"siteId"` + ZoneId int `json:"zoneId"` + Inventory json.RawMessage `json:"inventory,omitempty"` + Visitor json.RawMessage `json:"visitor,omitempty"` + Video rubiconVideoParams `json:"video"` +} + type bidRequestExt struct { Prebid bidRequestExtPrebid `json:"prebid"` } @@ -111,6 +134,15 @@ type rubiconBannerExt struct { } // ***** Video Extension ***** +type rubiconVideoParams struct { + Language string `json:"language,omitempty"` + PlayerHeight int `json:"playerHeight,omitempty"` + PlayerWidth int `json:"playerWidth,omitempty"` + VideoSizeID int `json:"size_id,omitempty"` + Skip int `json:"skip,omitempty"` + SkipDelay int `json:"skipdelay,omitempty"` +} + type rubiconVideoExt struct { Skip int `json:"skip,omitempty"` SkipDelay int `json:"skipdelay,omitempty"` @@ -122,6 +154,19 @@ type rubiconVideoExtRP struct { SizeID int `json:"size_id,omitempty"` } +type rubiconTargetingExt struct { + RP rubiconTargetingExtRP `json:"rp"` +} + +type rubiconTargetingExtRP struct { + Targeting []rubiconTargetingObj `json:"targeting"` +} + +type rubiconTargetingObj struct { + Key string `json:"key"` + Values []string `json:"values"` +} + type rubiconDeviceExtRP struct { PixelRatio float64 `json:"pixelratio"` } @@ -130,6 +175,10 @@ type rubiconDeviceExt struct { RP rubiconDeviceExtRP `json:"rp"` } +type rubiconUser struct { + Language string `json:"language"` +} + type rubiconBidResponse struct { openrtb2.BidResponse SeatBid []rubiconSeatBid `json:"seatbid,omitempty"` @@ -304,6 +353,273 @@ func parseRubiconSizes(sizes []openrtb2.Format) (primary int, alt []int, err err return } +func (a *RubiconAdapter) callOne(ctx context.Context, 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") + httpReq.Header.Add("User-Agent", "prebid-server/1.0") + httpReq.SetBasicAuth(a.XAPIUsername, a.XAPIPassword) + + rubiResp, e := ctxhttp.Do(ctx, a.http.Client, httpReq) + if e != nil { + err = e + return + } + + defer rubiResp.Body.Close() + body, _ := ioutil.ReadAll(rubiResp.Body) + result.ResponseBody = string(body) + + result.StatusCode = rubiResp.StatusCode + + if rubiResp.StatusCode == 204 { + return + } + + if rubiResp.StatusCode == http.StatusBadRequest { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), + } + } + + if rubiResp.StatusCode != http.StatusOK { + err = &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), + } + return + } + + var bidResp openrtb2.BidResponse + err = json.Unmarshal(body, &bidResp) + if err != nil { + err = &errortypes.BadServerResponse{ + Message: err.Error(), + } + return + } + if len(bidResp.SeatBid) == 0 { + return + } + if len(bidResp.SeatBid[0].Bid) == 0 { + return + } + bid := bidResp.SeatBid[0].Bid[0] + + result.Bid = &pbs.PBSBid{ + AdUnitCode: bid.ImpID, + Price: bid.Price, + Adm: bid.AdM, + Creative_id: bid.CrID, + // for video, the width and height are undefined as there's no corresponding return value from XAPI + Width: bid.W, + Height: bid.H, + DealId: bid.DealID, + } + + // Pull out any server-side determined targeting + var rpExtTrg rubiconTargetingExt + + if err := json.Unmarshal([]byte(bid.Ext), &rpExtTrg); err == nil { + // Converting string => array(string) to string => string + targeting := make(map[string]string) + + // Only pick off the first for now + for _, target := range rpExtTrg.RP.Targeting { + targeting[target.Key] = target.Values[0] + } + + result.Bid.AdServerTargeting = targeting + } + + return +} + +type callOneObject struct { + requestJson bytes.Buffer + mediaType pbs.MediaType +} + +func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + callOneObjects := make([]callOneObject, 0, len(bidder.AdUnits)) + supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + + rubiReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), supportedMediaTypes) + if err != nil { + return nil, err + } + + rubiReqImpCopy := rubiReq.Imp + + for i, unit := range bidder.AdUnits { + // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply + if len(rubiReqImpCopy) <= i { + break + } + // Only grab this ad unit + // Not supporting multi-media-type add-unit yet + thisImp := rubiReqImpCopy[i] + + // Amend it with RP-specific information + var params rubiconParams + err = json.Unmarshal(unit.Params, ¶ms) + if err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + + var mint, mintVersion string + mint = "prebid" + mintVersion = req.SDK.Source + "_" + req.SDK.Platform + "_" + req.SDK.Version + track := rubiconImpExtRPTrack{Mint: mint, MintVersion: mintVersion} + + impExt := rubiconImpExt{RP: rubiconImpExtRP{ + ZoneID: params.ZoneId, + Target: params.Inventory, + Track: track, + }} + thisImp.Ext, err = json.Marshal(&impExt) + if err != nil { + continue + } + + // Copy the $.user object and amend with $.user.ext.rp.target + // Copy avoids race condition since it points to ref & shared with other adapters + userCopy := *rubiReq.User + userExt := rubiconUserExt{RP: rubiconUserExtRP{Target: params.Visitor}} + userCopy.Ext, err = json.Marshal(&userExt) + // Assign back our copy + rubiReq.User = &userCopy + + deviceCopy := *rubiReq.Device + deviceExt := rubiconDeviceExt{RP: rubiconDeviceExtRP{PixelRatio: rubiReq.Device.PxRatio}} + deviceCopy.Ext, err = json.Marshal(&deviceExt) + rubiReq.Device = &deviceCopy + + if thisImp.Video != nil { + + videoSizeId := params.Video.VideoSizeID + if videoSizeId == 0 { + resolvedSizeId, err := resolveVideoSizeId(thisImp.Video.Placement, thisImp.Instl, thisImp.ID) + if err == nil { + videoSizeId = resolvedSizeId + } else { + continue + } + } + + videoExt := rubiconVideoExt{Skip: params.Video.Skip, SkipDelay: params.Video.SkipDelay, RP: rubiconVideoExtRP{SizeID: videoSizeId}} + thisImp.Video.Ext, err = json.Marshal(&videoExt) + } else { + primarySizeID, altSizeIDs, err := parseRubiconSizes(unit.Sizes) + if err != nil { + continue + } + bannerExt := rubiconBannerExt{RP: rubiconBannerExtRP{SizeID: primarySizeID, AltSizeIDs: altSizeIDs, MIME: "text/html"}} + thisImp.Banner.Ext, err = json.Marshal(&bannerExt) + } + + siteExt := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: params.SiteId}} + pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: params.AccountId}} + var rubiconUser rubiconUser + err = json.Unmarshal(req.PBSUser, &rubiconUser) + + if rubiReq.Site != nil { + siteCopy := *rubiReq.Site + siteCopy.Ext, err = json.Marshal(&siteExt) + siteCopy.Publisher = &openrtb2.Publisher{} + siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) + siteCopy.Content = &openrtb2.Content{} + siteCopy.Content.Language = rubiconUser.Language + rubiReq.Site = &siteCopy + } else { + site := &openrtb2.Site{} + site.Content = &openrtb2.Content{} + site.Content.Language = rubiconUser.Language + rubiReq.Site = site + } + + if rubiReq.App != nil { + appCopy := *rubiReq.App + appCopy.Ext, err = json.Marshal(&siteExt) + appCopy.Publisher = &openrtb2.Publisher{} + appCopy.Publisher.Ext, err = json.Marshal(&pubExt) + rubiReq.App = &appCopy + } + + rubiReq.Imp = []openrtb2.Imp{thisImp} + + var reqBuffer bytes.Buffer + err = json.NewEncoder(&reqBuffer).Encode(rubiReq) + if err != nil { + return nil, err + } + callOneObjects = append(callOneObjects, callOneObject{reqBuffer, unit.MediaTypes[0]}) + } + if len(callOneObjects) == 0 { + return nil, &errortypes.BadInput{ + Message: "Invalid ad unit/imp", + } + } + + ch := make(chan adapters.CallOneResult) + for _, obj := range callOneObjects { + go func(bidder *pbs.PBSBidder, reqJSON bytes.Buffer, mediaType pbs.MediaType) { + result, err := a.callOne(ctx, 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 + } else { + // no need to check whether mediaTypes is nil or length of zero, pbs.ParsePBSRequest will cover + // these cases. + // for media types other than banner and video, pbs.ParseMediaType will throw error. + // we may want to create a map/switch cases to support more media types in the future. + if mediaType == pbs.MEDIA_TYPE_VIDEO { + result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeVideo) + } else { + result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeBanner) + } + } + } + ch <- result + }(bidder, obj.requestJson, obj.mediaType) + } + + bids := make(pbs.PBSBidSlice, 0) + for i := 0; i < len(callOneObjects); i++ { + result := <-ch + if result.Bid != nil && result.Bid.Price != 0 { + bids = append(bids, result.Bid) + } + if req.IsDebug { + debug := &pbs.BidderDebug{ + RequestURI: a.URI, + RequestBody: callOneObjects[i].requestJson.String(), + StatusCode: result.StatusCode, + ResponseBody: result.ResponseBody, + } + bidder.Debug = append(bidder.Debug, debug) + } + if result.Error != nil { + if glog.V(2) { + glog.Infof("Error from rubicon adapter: %v", result.Error) + } + err = result.Error + } + } + + if len(bids) == 0 { + return nil, err + } + return bids, nil +} + func resolveVideoSizeId(placement openrtb2.VideoPlacementType, instl int8, impId string) (sizeID int, err error) { if placement != 0 { if placement == 1 { @@ -349,6 +665,19 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } +func NewRubiconLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, uri string, xuser string, xpass string, tracker string) *RubiconAdapter { + a := adapters.NewHTTPAdapter(httpConfig) + + uri = appendTrackerToUrl(uri, tracker) + + return &RubiconAdapter{ + http: a, + URI: uri, + XAPIUsername: xuser, + XAPIPassword: xpass, + } +} + func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) errs := make([]error, 0, len(request.Imp)) diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 0d88937b6da..9bfa04fa78f 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1,17 +1,27 @@ package rubicon import ( + "bytes" + "context" "encoding/json" "errors" + "fmt" + "io/ioutil" "net/http" + "net/http/httptest" "strconv" + "strings" "testing" + "time" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -38,17 +48,291 @@ type rubiSetNetworkIdTestScenario struct { isNetworkIdSet bool } +type rubiTagInfo struct { + code string + zoneID int + bid float64 + content string + adServerTargeting map[string]string + mediaType string +} + type rubiBidInfo struct { - domain string - page string - deviceIP string - deviceUA string - buyerUID string - devicePxRatio float64 + domain string + page string + accountID int + siteID int + tags []rubiTagInfo + deviceIP string + deviceUA string + buyerUID string + xapiuser string + xapipass string + delay time.Duration + visitorTargeting string + inventoryTargeting string + sdkVersion string + sdkPlatform string + sdkSource string + devicePxRatio float64 } var rubidata rubiBidInfo +func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { + defer func() { + err := r.Body.Close() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }() + + 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 len(breq.Imp) > 1 { + http.Error(w, "Rubicon adapter only supports one Imp per request", http.StatusInternalServerError) + return + } + imp := breq.Imp[0] + var rix rubiconImpExt + err = json.Unmarshal(imp.Ext, &rix) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + impTargetingString, _ := json.Marshal(&rix.RP.Target) + if string(impTargetingString) != rubidata.inventoryTargeting { + http.Error(w, fmt.Sprintf("Inventory FPD targeting '%s' doesn't match '%s'", string(impTargetingString), rubidata.inventoryTargeting), http.StatusInternalServerError) + return + } + if rix.RP.Track.Mint != "prebid" { + http.Error(w, fmt.Sprintf("Track mint '%s' doesn't match '%s'", rix.RP.Track.Mint, "prebid"), http.StatusInternalServerError) + return + } + mintVersionString := rubidata.sdkSource + "_" + rubidata.sdkPlatform + "_" + rubidata.sdkVersion + if rix.RP.Track.MintVersion != mintVersionString { + http.Error(w, fmt.Sprintf("Track mint version '%s' doesn't match '%s'", rix.RP.Track.MintVersion, mintVersionString), http.StatusInternalServerError) + return + } + + ix := -1 + + for i, tag := range rubidata.tags { + if rix.RP.ZoneID == tag.zoneID { + ix = i + } + } + if ix == -1 { + http.Error(w, fmt.Sprintf("Zone %d not found", rix.RP.ZoneID), http.StatusInternalServerError) + return + } + + resp := openrtb2.BidResponse{ + ID: "test-response-id", + BidID: "test-bid-id", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "RUBICON", + Bid: make([]openrtb2.Bid, 2), + }, + }, + } + + if imp.Banner != nil { + var bix rubiconBannerExt + err = json.Unmarshal(imp.Banner.Ext, &bix) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if bix.RP.SizeID != 15 { // 300x250 + http.Error(w, fmt.Sprintf("Primary size ID isn't 15"), http.StatusInternalServerError) + return + } + if len(bix.RP.AltSizeIDs) != 1 || bix.RP.AltSizeIDs[0] != 10 { // 300x600 + http.Error(w, fmt.Sprintf("Alt size ID isn't 10"), http.StatusInternalServerError) + return + } + if bix.RP.MIME != "text/html" { + http.Error(w, fmt.Sprintf("MIME isn't text/html"), http.StatusInternalServerError) + return + } + } + + if imp.Video != nil { + var vix rubiconVideoExt + err = json.Unmarshal(imp.Video.Ext, &vix) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if len(imp.Video.MIMEs) == 0 { + http.Error(w, fmt.Sprintf("Empty imp.video.mimes array"), http.StatusInternalServerError) + return + } + if len(imp.Video.Protocols) == 0 { + http.Error(w, fmt.Sprintf("Empty imp.video.protocols array"), http.StatusInternalServerError) + return + } + for _, protocol := range imp.Video.Protocols { + if protocol < 1 || protocol > 8 { + http.Error(w, fmt.Sprintf("Invalid video protocol %d", protocol), http.StatusInternalServerError) + return + } + } + } + + targeting := "{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}" + rawTargeting := json.RawMessage(targeting) + + resp.SeatBid[0].Bid[0] = openrtb2.Bid{ + ID: "random-id", + ImpID: imp.ID, + Price: rubidata.tags[ix].bid, + AdM: rubidata.tags[ix].content, + Ext: rawTargeting, + } + + if breq.Site == nil { + http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) + return + } + if breq.Site.Domain != rubidata.domain { + http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, rubidata.domain), http.StatusInternalServerError) + return + } + if breq.Site.Page != rubidata.page { + http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, rubidata.page), http.StatusInternalServerError) + return + } + var rsx rubiconSiteExt + err = json.Unmarshal(breq.Site.Ext, &rsx) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if rsx.RP.SiteID != rubidata.siteID { + http.Error(w, fmt.Sprintf("SiteID '%d' doesn't match '%d", rsx.RP.SiteID, rubidata.siteID), http.StatusInternalServerError) + return + } + if breq.Site.Publisher == nil { + http.Error(w, fmt.Sprintf("No site.publisher object sent"), http.StatusInternalServerError) + return + } + var rpx rubiconPubExt + err = json.Unmarshal(breq.Site.Publisher.Ext, &rpx) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if rpx.RP.AccountID != rubidata.accountID { + http.Error(w, fmt.Sprintf("AccountID '%d' doesn't match '%d'", rpx.RP.AccountID, rubidata.accountID), http.StatusInternalServerError) + return + } + if breq.Device.UA != rubidata.deviceUA { + http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s'", breq.Device.UA, rubidata.deviceUA), http.StatusInternalServerError) + return + } + if breq.Device.IP != rubidata.deviceIP { + http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s'", breq.Device.IP, rubidata.deviceIP), http.StatusInternalServerError) + return + } + if breq.Device.PxRatio != rubidata.devicePxRatio { + http.Error(w, fmt.Sprintf("Pixel ratio '%f' doesn't match '%f'", breq.Device.PxRatio, rubidata.devicePxRatio), http.StatusInternalServerError) + return + } + if breq.User.BuyerUID != rubidata.buyerUID { + http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s'", breq.User.BuyerUID, rubidata.buyerUID), http.StatusInternalServerError) + return + } + + var rux rubiconUserExt + err = json.Unmarshal(breq.User.Ext, &rux) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + userTargetingString, _ := json.Marshal(&rux.RP.Target) + if string(userTargetingString) != rubidata.visitorTargeting { + http.Error(w, fmt.Sprintf("User FPD targeting '%s' doesn't match '%s'", string(userTargetingString), rubidata.visitorTargeting), http.StatusInternalServerError) + return + } + + if rubidata.delay > 0 { + <-time.After(rubidata.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 TestRubiconBasicResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) + defer server.Close() + + an, ctx, pbReq := CreatePrebidRequest(server, t) + + bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + assert.Nil(t, err, "Should not have gotten an error: %v", err) + assert.Equal(t, 2, len(bids), "Received %d bids instead of 3", len(bids)) + + for _, bid := range bids { + matched := false + for _, tag := range rubidata.tags { + if bid.AdUnitCode == tag.code { + matched = true + + assert.Equal(t, "rubicon", bid.BidderCode, "Incorrect BidderCode '%s'", bid.BidderCode) + + assert.Equal(t, tag.bid, bid.Price, "Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) + + assert.Equal(t, tag.content, bid.Adm, "Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) + + assert.Equal(t, bid.AdServerTargeting, tag.adServerTargeting, + "Incorrect targeting '%+v' expected '%+v'", bid.AdServerTargeting, tag.adServerTargeting) + + assert.Equal(t, tag.mediaType, bid.CreativeMediaType, "Incorrect media type '%s' expected '%s'", bid.CreativeMediaType, tag.mediaType) + } + } + assert.True(t, matched, "Received bid for unknown ad unit '%s'", bid.AdUnitCode) + } + + // same test but with request timing out + rubidata.delay = 20 * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + bids, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + assert.NotNil(t, err, "Should have gotten a timeout error: %v", err) +} + +func TestRubiconUserSyncInfo(t *testing.T) { + conf := *adapters.DefaultHTTPAdapterConfig + an := NewRubiconLegacyAdapter(&conf, "uri", "xuser", "xpass", "pbs-test-tracker") + + assert.Equal(t, "rubicon", an.Name(), "Name '%s' != 'rubicon'", an.Name()) + + assert.False(t, an.SkipNoCookies(), "SkipNoCookies should be false") +} + func getTestSizes() map[int]openrtb2.Format { return map[int]openrtb2.Format{ 15: {W: 300, H: 250}, @@ -419,6 +703,368 @@ func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 { 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) + })) + defer server.Close() + + an, ctx, pbReq := CreatePrebidRequest(server, t) + bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + + assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) + + assert.Equal(t, 204, pbReq.Bidders[0].Debug[0].StatusCode, + "StatusCode should be 204 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) + + assert.Nil(t, err, "Should not have gotten an error: %v", err) +} + +func TestNotFoundResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer server.Close() + + an, ctx, pbReq := CreatePrebidRequest(server, t) + _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + + assert.Equal(t, 404, pbReq.Bidders[0].Debug[0].StatusCode, + "StatusCode should be 404 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) + + assert.NotNil(t, err, "Should have gotten an error: %v", err) + + assert.True(t, strings.HasPrefix(err.Error(), "HTTP status 404"), + "Should start with 'HTTP status' instead of: %v", err.Error()) +} + +func TestWrongFormatResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("This is text.")) + })) + defer server.Close() + + an, ctx, pbReq := CreatePrebidRequest(server, t) + _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + + assert.Equal(t, 200, pbReq.Bidders[0].Debug[0].StatusCode, + "StatusCode should be 200 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) + + assert.NotNil(t, err, "Should have gotten an error: %v", err) + + assert.True(t, strings.HasPrefix(err.Error(), "invalid character"), + "Should start with 'invalid character' instead of: %v", err) +} + +func TestZeroSeatBidResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp := openrtb2.BidResponse{ + ID: "test-response-id", + BidID: "test-bid-id", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{}, + } + js, _ := json.Marshal(resp) + w.Write(js) + })) + defer server.Close() + + an, ctx, pbReq := CreatePrebidRequest(server, t) + bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + + assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) + + assert.Nil(t, err, "Should not have gotten an error: %v", err) +} + +func TestEmptyBidResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp := openrtb2.BidResponse{ + ID: "test-response-id", + BidID: "test-bid-id", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "RUBICON", + Bid: make([]openrtb2.Bid, 0), + }, + }, + } + js, _ := json.Marshal(resp) + w.Write(js) + })) + defer server.Close() + + an, ctx, pbReq := CreatePrebidRequest(server, t) + bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + + assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) + + assert.Nil(t, err, "Should not have gotten an error: %v", err) +} + +func TestWrongBidIdResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp := openrtb2.BidResponse{ + ID: "test-response-id", + BidID: "test-bid-id", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "RUBICON", + Bid: make([]openrtb2.Bid, 2), + }, + }, + } + resp.SeatBid[0].Bid[0] = openrtb2.Bid{ + ID: "random-id", + ImpID: "zma", + Price: 1.67, + AdM: "zma", + Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), + } + js, _ := json.Marshal(resp) + w.Write(js) + })) + defer server.Close() + + an, ctx, pbReq := CreatePrebidRequest(server, t) + bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + + assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) + + assert.NotNil(t, err, "Should not have gotten an error: %v", err) + + assert.True(t, strings.HasPrefix(err.Error(), "Unknown ad unit code"), + "Should start with 'Unknown ad unit code' instead of: %v", err) +} + +func TestZeroPriceBidResponse(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp := openrtb2.BidResponse{ + ID: "test-response-id", + BidID: "test-bid-id", + Cur: "USD", + SeatBid: []openrtb2.SeatBid{ + { + Seat: "RUBICON", + Bid: make([]openrtb2.Bid, 1), + }, + }, + } + resp.SeatBid[0].Bid[0] = openrtb2.Bid{ + ID: "test-bid-id", + ImpID: "first-tag", + Price: 0, + AdM: "zma", + Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), + } + js, _ := json.Marshal(resp) + w.Write(js) + })) + defer server.Close() + + an, ctx, pbReq := CreatePrebidRequest(server, t) + b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + + assert.Nil(t, b, "\n\n\n0 price bids are being included %d, err : %v", len(b), err) +} + +func TestDifferentRequest(t *testing.T) { + SIZE_ID := getTestSizes() + server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) + defer server.Close() + + an, ctx, pbReq := CreatePrebidRequest(server, t) + + // test app not nil + pbReq.App = &openrtb2.App{ + ID: "com.test", + Name: "testApp", + } + + _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + assert.NotNil(t, err, "Should have gotten an error: %v", err) + + // set app back to normal + pbReq.App = nil + + // test video media type + pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO} + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + assert.NotNil(t, err, "Should have gotten an error: %v", err) + + // set media back to normal + pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} + + // test wrong params + pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %s, \"siteId\": %d, \"visitor\": %s, \"inventory\": %s}", "zma", rubidata.siteID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + assert.NotNil(t, err, "Should have gotten an error: %v", err) + + // set params back to normal + pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", 8394, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) + + // test invalid size + pbReq.Bidders[0].AdUnits[0].Sizes = []openrtb2.Format{ + { + W: 2222, + H: 333, + }, + } + pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ + { + W: 222, + H: 3333, + }, + { + W: 350, + H: 270, + }, + } + pbReq.Bidders[0].AdUnits = pbReq.Bidders[0].AdUnits[:len(pbReq.Bidders[0].AdUnits)-1] + b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + assert.NotNil(t, err, "Should have gotten an error: %v", err) + + pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ + { + W: 222, + H: 3333, + }, + SIZE_ID[10], + SIZE_ID[15], + } + b, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + assert.Nil(t, err, "Should have not gotten an error: %v", err) + + assert.Equal(t, 1, len(b), + "Filtering bids based on ad unit sizes failed. Got %d bids instead of 1, error = %v", len(b), err) +} + +func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdapter, ctx context.Context, pbReq *pbs.PBSRequest) { + SIZE_ID := getTestSizes() + rubidata = rubiBidInfo{ + domain: "nytimes.com", + page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", + accountID: 7891, + siteID: 283282, + tags: make([]rubiTagInfo, 3), + deviceIP: "25.91.96.36", + deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", + buyerUID: "need-an-actual-rp-id", + visitorTargeting: "[\"v1\",\"v2\"]", + inventoryTargeting: "[\"i1\",\"i2\"]", + sdkVersion: "2.0.0", + sdkPlatform: "iOS", + sdkSource: "some-sdk", + devicePxRatio: 4.0, + } + + targeting := make(map[string]string, 2) + targeting["key1"] = "value1" + targeting["key2"] = "value2" + + rubidata.tags[0] = rubiTagInfo{ + code: "first-tag", + zoneID: 8394, + bid: 1.67, + adServerTargeting: targeting, + mediaType: "banner", + } + rubidata.tags[1] = rubiTagInfo{ + code: "second-tag", + zoneID: 8395, + bid: 3.22, + adServerTargeting: targeting, + mediaType: "banner", + } + rubidata.tags[2] = rubiTagInfo{ + code: "video-tag", + zoneID: 7780, + bid: 23.12, + adServerTargeting: targeting, + mediaType: "video", + } + + conf := *adapters.DefaultHTTPAdapterConfig + an = NewRubiconLegacyAdapter(&conf, "uri", rubidata.xapiuser, rubidata.xapipass, "pbs-test-tracker") + an.URI = server.URL + + pbin := pbs.PBSRequest{ + AdUnits: make([]pbs.AdUnit, 3), + Device: &openrtb2.Device{PxRatio: rubidata.devicePxRatio}, + SDK: &pbs.SDK{Source: rubidata.sdkSource, Platform: rubidata.sdkPlatform, Version: rubidata.sdkVersion}, + } + + for i, tag := range rubidata.tags { + pbin.AdUnits[i] = pbs.AdUnit{ + Code: tag.code, + MediaTypes: []string{tag.mediaType}, + Sizes: []openrtb2.Format{ + SIZE_ID[10], + SIZE_ID[15], + }, + Bids: []pbs.Bids{ + { + BidderCode: "rubicon", + BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), + Params: json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", tag.zoneID, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)), + }, + }, + } + if tag.mediaType == "video" { + pbin.AdUnits[i].Video = pbs.PBSVideo{ + Mimes: []string{"video/mp4"}, + Minduration: 15, + Maxduration: 30, + Startdelay: 5, + Skippable: 0, + PlaybackMethod: 1, + Protocols: []int8{1, 2, 3, 4, 5}, + } + } + } + + body := new(bytes.Buffer) + err := json.NewEncoder(body).Encode(pbin) + if err != nil { + t.Fatalf("Json encoding failed: %v", err) + } + + req := httptest.NewRequest("POST", server.URL, body) + req.Header.Add("Referer", rubidata.page) + req.Header.Add("User-Agent", rubidata.deviceUA) + req.Header.Add("X-Real-IP", rubidata.deviceIP) + + pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) + pc.TrySync("rubicon", rubidata.buyerUID) + 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) + pbReq.IsDebug = true + + assert.Nil(t, err, "ParsePBSRequest failed: %v", err) + + assert.Equal(t, 1, len(pbReq.Bidders), + "ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) + + assert.Equal(t, "rubicon", pbReq.Bidders[0].BidderCode, + "ParsePBSRequest returned invalid bidder") + + ctx = context.TODO() + return +} + func TestOpenRTBRequest(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) diff --git a/adapters/sharethrough/utils_test.go b/adapters/sharethrough/utils_test.go index 70e97947880..b842cf0b0c0 100644 --- a/adapters/sharethrough/utils_test.go +++ b/adapters/sharethrough/utils_test.go @@ -151,6 +151,10 @@ type userAgentTest struct { expected bool } +type userAgentFailureTest struct { + input string +} + func runUserAgentTests(tests map[string]userAgentTest, fn func(string) bool, t *testing.T) { for testName, test := range tests { t.Logf("Test case: %s\n", testName) diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go index 0ff71cdb0e5..690d5f59f67 100644 --- a/adapters/sonobi/sonobi.go +++ b/adapters/sonobi/sonobi.go @@ -25,6 +25,10 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } +type sonobiParams struct { + TagID string `json:"TagID"` +} + // MakeRequests Makes the OpenRTB request payload func (a *SonobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error @@ -148,3 +152,9 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), } } + +func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { + if len(headerValue) > 0 { + headers.Add(headerName, headerValue) + } +} diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 98264ce3a1b..40969d3638e 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -1,23 +1,176 @@ package sovrn import ( + "bytes" + "context" "encoding/json" "fmt" + "io/ioutil" "net/http" "net/url" + "sort" "strconv" "strings" + "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" - - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/pbs" + "golang.org/x/net/context/ctxhttp" ) type SovrnAdapter struct { - URI string + http *adapters.HTTPAdapter + URI string +} + +// Name - export adapter name */ +func (s *SovrnAdapter) Name() string { + return "sovrn" +} + +// FamilyName used for cookies and such +func (s *SovrnAdapter) FamilyName() string { + return "sovrn" +} + +func (s *SovrnAdapter) SkipNoCookies() bool { + return false +} + +// Call send bid requests to sovrn and receive responses +func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} + sReq, err := adapters.MakeOpenRTBGeneric(req, bidder, s.FamilyName(), supportedMediaTypes) + + if err != nil { + return nil, err + } + + sovrnReq := openrtb2.BidRequest{ + ID: sReq.ID, + Imp: sReq.Imp, + Site: sReq.Site, + User: sReq.User, + Regs: sReq.Regs, + } + + // add tag ids to impressions + for i, unit := range bidder.AdUnits { + var params openrtb_ext.ExtImpSovrn + err = json.Unmarshal(unit.Params, ¶ms) + if err != nil { + return nil, err + } + + // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply + if len(sovrnReq.Imp) <= i { + break + } + sovrnReq.Imp[i].TagID = getTagid(params) + } + + reqJSON, err := json.Marshal(sovrnReq) + if err != nil { + return nil, err + } + + debug := &pbs.BidderDebug{ + RequestURI: s.URI, + } + + httpReq, _ := http.NewRequest("POST", s.URI, bytes.NewReader(reqJSON)) + httpReq.Header.Set("Content-Type", "application/json") + if sReq.Device != nil { + addHeaderIfNonEmpty(httpReq.Header, "User-Agent", sReq.Device.UA) + addHeaderIfNonEmpty(httpReq.Header, "X-Forwarded-For", sReq.Device.IP) + addHeaderIfNonEmpty(httpReq.Header, "Accept-Language", sReq.Device.Language) + if sReq.Device.DNT != nil { + addHeaderIfNonEmpty(httpReq.Header, "DNT", strconv.Itoa(int(*sReq.Device.DNT))) + } + } + if sReq.User != nil { + userID := strings.TrimSpace(sReq.User.BuyerUID) + if len(userID) > 0 { + httpReq.AddCookie(&http.Cookie{Name: "ljt_reader", Value: userID}) + } + } + sResp, err := ctxhttp.Do(ctx, s.http.Client, httpReq) + if err != nil { + return nil, err + } + defer sResp.Body.Close() + + debug.StatusCode = sResp.StatusCode + + if sResp.StatusCode == http.StatusNoContent { + return nil, nil + } + + body, err := ioutil.ReadAll(sResp.Body) + if err != nil { + return nil, err + } + responseBody := string(body) + + if sResp.StatusCode == http.StatusBadRequest { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status %d; body: %s", sResp.StatusCode, responseBody), + } + } + + if sResp.StatusCode != http.StatusOK { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status %d; body: %s", sResp.StatusCode, responseBody), + } + } + + if req.IsDebug { + debug.RequestBody = string(reqJSON) + bidder.Debug = append(bidder.Debug, debug) + debug.ResponseBody = responseBody + } + + var bidResp openrtb2.BidResponse + err = json.Unmarshal(body, &bidResp) + if err != nil { + return nil, &errortypes.BadServerResponse{ + Message: err.Error(), + } + } + + bids := make(pbs.PBSBidSlice, 0) + + for _, sb := range bidResp.SeatBid { + for _, bid := range sb.Bid { + bidID := bidder.LookupBidID(bid.ImpID) + if bidID == "" { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), + } + } + + adm, _ := url.QueryUnescape(bid.AdM) + pbid := pbs.PBSBid{ + BidID: bidID, + AdUnitCode: bid.ImpID, + BidderCode: bidder.BidderCode, + Price: bid.Price, + Adm: adm, + Creative_id: bid.CrID, + Width: bid.W, + Height: bid.H, + DealId: bid.DealID, + NURL: bid.NURL, + } + bids = append(bids, &pbid) + } + } + + sort.Sort(bids) + return bids, nil } func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -150,6 +303,14 @@ func getTagid(sovrnExt openrtb_ext.ExtImpSovrn) string { } } +// NewSovrnLegacyAdapter create a new SovrnAdapter instance +func NewSovrnLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *SovrnAdapter { + return &SovrnAdapter{ + http: adapters.NewHTTPAdapter(config), + URI: endpoint, + } +} + // Builder builds a new instance of the Sovrn adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &SovrnAdapter{ diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index 49ed52844f3..407c505437a 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -1,11 +1,28 @@ package sovrn import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http/httptest" "testing" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" + + "context" + "net/http" + + "strconv" + "time" + + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" ) func TestJsonSamples(t *testing.T) { @@ -18,3 +35,262 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "sovrntest", bidder) } + +// ---------------------------------------------------------------------------- +// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we +// clean up the existing code and make everything openrtb2. + +var testSovrnUserId = "SovrnUser123" +var testUserAgent = "user-agent-test" +var testUrl = "http://news.pub/topnews" +var testIp = "123.123.123.123" + +func TestSovrnAdapterNames(t *testing.T) { + adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://sovrn/rtb/bid") + adapterstest.VerifyStringValue(adapter.Name(), "sovrn", t) + adapterstest.VerifyStringValue(adapter.FamilyName(), "sovrn", t) +} + +func TestSovrnAdapter_SkipNoCookies(t *testing.T) { + adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://sovrn/rtb/bid") + adapterstest.VerifyBoolValue(adapter.SkipNoCookies(), false, t) +} + +func TestSovrnOpenRtbRequest(t *testing.T) { + service := CreateSovrnService(adapterstest.BidOnTags("")) + server := service.Server + ctx := context.Background() + req := SampleSovrnRequest(1, t) + bidder := req.Bidders[0] + adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + adapter.Call(ctx, req, bidder) + + adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) + adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "123456", t) + adapterstest.VerifyBannerSize(service.LastBidRequest.Imp[0].Banner, 728, 90, t) + checkHttpRequest(*service.LastHttpRequest, t) +} + +func TestSovrnBiddingBehavior(t *testing.T) { + service := CreateSovrnService(adapterstest.BidOnTags("123456")) + server := service.Server + ctx := context.TODO() + req := SampleSovrnRequest(1, t) + bidder := req.Bidders[0] + adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + bids, _ := adapter.Call(ctx, req, bidder) + + adapterstest.VerifyIntValue(len(bids), 1, t) + adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) + adapterstest.VerifyStringValue(bids[0].BidderCode, "sovrn", t) + adapterstest.VerifyStringValue(bids[0].Adm, "
This is an Ad
", t) + adapterstest.VerifyStringValue(bids[0].Creative_id, "Cr-234", t) + adapterstest.VerifyIntValue(int(bids[0].Width), 728, t) + adapterstest.VerifyIntValue(int(bids[0].Height), 90, t) + adapterstest.VerifyIntValue(int(bids[0].Price*100), 210, t) + checkHttpRequest(*service.LastHttpRequest, t) +} + +/** + * Verify bidding behavior on multiple impressions, some impressions make a bid + */ +func TestSovrntMultiImpPartialBidding(t *testing.T) { + // setup server endpoint to return bid. + service := CreateSovrnService(adapterstest.BidOnTags("123456")) + server := service.Server + ctx := context.TODO() + req := SampleSovrnRequest(2, t) + bidder := req.Bidders[0] + adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + bids, _ := adapter.Call(ctx, req, bidder) + // two impressions sent. + // number of bids should be 1 + adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) + adapterstest.VerifyIntValue(len(bids), 1, t) + adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) + checkHttpRequest(*service.LastHttpRequest, t) +} + +/** + * Verify bidding behavior on multiple impressions, all impressions passed back. + */ +func TestSovrnMultiImpAllBid(t *testing.T) { + // setup server endpoint to return bid. + service := CreateSovrnService(adapterstest.BidOnTags("123456,123457")) + server := service.Server + ctx := context.TODO() + req := SampleSovrnRequest(2, t) + bidder := req.Bidders[0] + adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + bids, _ := adapter.Call(ctx, req, bidder) + // two impressions sent. + // number of bids should be 1 + adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) + adapterstest.VerifyIntValue(len(bids), 2, t) + adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) + adapterstest.VerifyStringValue(bids[1].AdUnitCode, "div-adunit-2", t) + checkHttpRequest(*service.LastHttpRequest, t) +} + +func checkHttpRequest(req http.Request, t *testing.T) { + adapterstest.VerifyStringValue(req.Header.Get("Accept-Language"), "murican", t) + var cookie, _ = req.Cookie("ljt_reader") + adapterstest.VerifyStringValue((*cookie).Value, testSovrnUserId, t) + adapterstest.VerifyStringValue(req.Header.Get("User-Agent"), testUserAgent, t) + adapterstest.VerifyStringValue(req.Header.Get("Content-Type"), "application/json", t) + adapterstest.VerifyStringValue(req.Header.Get("X-Forwarded-For"), testIp, t) + adapterstest.VerifyStringValue(req.Header.Get("DNT"), "0", t) +} + +func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { + dnt := int8(0) + device := openrtb2.Device{ + Language: "murican", + DNT: &dnt, + } + + user := openrtb2.User{ + ID: testSovrnUserId, + } + + req := pbs.PBSRequest{ + AccountID: "1", + AdUnits: make([]pbs.AdUnit, 2), + Device: &device, + User: &user, + } + + tagID := 123456 + + for i := 0; i < numberOfImpressions; i++ { + req.AdUnits[i] = pbs.AdUnit{ + Code: fmt.Sprintf("div-adunit-%d", i+1), + Sizes: []openrtb2.Format{ + { + W: 728, + H: 90, + }, + }, + Bids: []pbs.Bids{ + { + BidderCode: "sovrn", + BidID: fmt.Sprintf("Bid-%d", i+1), + Params: json.RawMessage(fmt.Sprintf("{\"tagid\": \"%s\" }", strconv.Itoa(tagID+i))), + }, + }, + } + + } + + body := new(bytes.Buffer) + err := json.NewEncoder(body).Encode(req) + if err != nil { + t.Fatalf("Error when serializing request") + } + + httpReq := httptest.NewRequest("POST", CreateSovrnService(adapterstest.BidOnTags("")).Server.URL, body) + httpReq.Header.Add("Referer", testUrl) + httpReq.Header.Add("User-Agent", testUserAgent) + httpReq.Header.Add("X-Forwarded-For", testIp) + pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) + pc.TrySync("sovrn", testSovrnUserId) + fakewriter := httptest.NewRecorder() + + pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) + httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) + // parse the http request + cacheClient, _ := dummycache.New() + hcc := config.HostCookie{} + + parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, cacheClient, &hcc) + if err != nil { + t.Fatalf("Error when parsing request: %v", err) + } + return parsedReq + +} + +func TestNoContentResponse(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + })) + defer server.Close() + + ctx := context.TODO() + req := SampleSovrnRequest(1, t) + bidder := req.Bidders[0] + adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + _, err := adapter.Call(ctx, req, bidder) + + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + +} + +func TestNotFoundResponse(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer server.Close() + + ctx := context.TODO() + req := SampleSovrnRequest(1, t) + bidder := req.Bidders[0] + adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + _, err := adapter.Call(ctx, req, bidder) + + adapterstest.VerifyStringValue(err.Error(), "HTTP status 404; body: ", t) + +} + +func CreateSovrnService(tagsToBid map[string]bool) adapterstest.OrtbMockService { + service := adapterstest.OrtbMockService{} + var lastBidRequest openrtb2.BidRequest + var lastHttpReq http.Request + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + lastHttpReq = *r + 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 + } + lastBidRequest = breq + var bids []openrtb2.Bid + for i, imp := range breq.Imp { + if tagsToBid[imp.TagID] { + bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) + } + } + + // serialize the bids to openrtb2.BidResponse + js, _ := json.Marshal(openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ + { + Bid: bids, + }, + }, + }) + w.Header().Set("Content-Type", "application/json") + w.Write(js) + })) + + service.Server = server + service.LastBidRequest = &lastBidRequest + service.LastHttpRequest = &lastHttpReq + + return service +} diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index 43853382354..a0721d98a2a 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -102,6 +102,8 @@ func NewFileLogger(filename string) (analytics.PBSAnalyticsModule, error) { } } +type fileAuctionObject analytics.AuctionObject + func jsonifyAuctionObject(ao *analytics.AuctionObject) string { type alias analytics.AuctionObject b, err := json.Marshal(&struct { diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 0e0b3634508..cb8f088d0bf 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -2,15 +2,59 @@ package pubstack import ( "encoding/json" + "io/ioutil" "net/http" "net/http/httptest" + "os" "testing" "time" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/stretchr/testify/assert" ) +func loadJSONFromFile() (*analytics.AuctionObject, error) { + req, err := os.Open("mocks/mock_openrtb_request.json") + if err != nil { + return nil, err + } + defer req.Close() + + reqCtn := openrtb2.BidRequest{} + reqPayload, err := ioutil.ReadAll(req) + if err != nil { + return nil, err + } + + err = json.Unmarshal(reqPayload, &reqCtn) + if err != nil { + return nil, err + } + + res, err := os.Open("mocks/mock_openrtb_response.json") + if err != nil { + return nil, err + } + defer res.Close() + + resCtn := openrtb2.BidResponse{} + resPayload, err := ioutil.ReadAll(res) + if err != nil { + return nil, err + } + + err = json.Unmarshal(resPayload, &resCtn) + if err != nil { + return nil, err + } + + return &analytics.AuctionObject{ + Request: &reqCtn, + Response: &resCtn, + }, nil +} + func TestPubstackModuleErrors(t *testing.T) { tests := []struct { description string diff --git a/cache/dummycache/dummycache.go b/cache/dummycache/dummycache.go new file mode 100644 index 00000000000..02fe726d043 --- /dev/null +++ b/cache/dummycache/dummycache.go @@ -0,0 +1,65 @@ +package dummycache + +import ( + "fmt" + + "github.com/prebid/prebid-server/cache" +) + +// Cache dummy config that will echo back results +type Cache struct { + accounts *accountService + config *configService +} + +// New creates new dummy.Cache +func New() (*Cache, error) { + return &Cache{ + accounts: &accountService{}, + config: &configService{}, + }, nil +} + +func (c *Cache) Accounts() cache.AccountsService { + return c.accounts +} +func (c *Cache) Config() cache.ConfigService { + return c.config +} + +// AccountService handles the account information +type accountService struct { +} + +// Get echos back the account +func (s *accountService) Get(id string) (*cache.Account, error) { + return &cache.Account{ + ID: id, + }, nil +} + +// ConfigService not supported, always returns an error +type configService struct { + c string +} + +// Get not supported, always returns an error +func (s *configService) Get(id string) (string, error) { + if s.c == "" { + return s.c, fmt.Errorf("No configuration provided") + } + return s.c, nil +} + +// Set will set a string in memory as the configuration +// this is so we can use it in tests such as pbs/pbsrequest_test.go +// it will ignore the id so this will pass tests +func (s *configService) Set(id, val string) error { + s.c = val + return nil +} + +// Close will always return nil +func (c *Cache) Close() error { + return nil +} diff --git a/cache/dummycache/dummycache_test.go b/cache/dummycache/dummycache_test.go new file mode 100644 index 00000000000..74004feaa38 --- /dev/null +++ b/cache/dummycache/dummycache_test.go @@ -0,0 +1,31 @@ +package dummycache + +import "testing" + +func TestDummyCache(t *testing.T) { + + c, _ := New() + + account, err := c.Accounts().Get("account1") + if err != nil { + t.Fatal(err) + } + + if account.ID != "account1" { + t.Error("Wrong account returned") + } + + if err := c.Config().Set("config", "abc123"); err != nil { + t.Errorf("Dummy config should return nil") + } + + cfg, err := c.Config().Get("config") + if err != nil { + t.Error("Dummy configs should be supported") + } + + if cfg != "abc123" { + t.Error("Dummy config did not return back expected string") + } + +} diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go new file mode 100644 index 00000000000..7bc4bea43f0 --- /dev/null +++ b/cache/filecache/filecache.go @@ -0,0 +1,123 @@ +package filecache + +import ( + "fmt" + "io/ioutil" + + "github.com/golang/glog" + "github.com/prebid/prebid-server/cache" + "gopkg.in/yaml.v2" +) + +type shared struct { + Configs map[string]string + Accounts map[string]bool +} + +// Cache is a file backed cache +type Cache struct { + shared *shared + accounts *accountService + config *configService +} + +type fileConfig struct { + ID string `yaml:"id"` + Config string `yaml:"config"` +} + +type fileCacheFile struct { + Configs []fileConfig `yaml:"configs"` + Accounts []string `yaml:"accounts"` +} + +// New will load the file into memory +func New(filename string) (*Cache, error) { + if glog.V(2) { + glog.Infof("Reading inventory urls from %s", filename) + } + + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + if glog.V(2) { + glog.Infof("Parsing filecache YAML") + } + + var u fileCacheFile + if err = yaml.Unmarshal(b, &u); err != nil { + return nil, err + } + + if glog.V(2) { + glog.Infof("Building URL map") + } + + s := &shared{} + + s.Configs = make(map[string]string, len(u.Configs)) + for _, config := range u.Configs { + s.Configs[config.ID] = config.Config + } + glog.Infof("Loaded %d configs", len(u.Configs)) + + s.Accounts = make(map[string]bool, len(u.Accounts)) + for _, Account := range u.Accounts { + s.Accounts[Account] = true + } + glog.Infof("Loaded %d accounts", len(u.Accounts)) + + return &Cache{ + shared: s, + accounts: &accountService{s}, + config: &configService{s}, + }, nil +} + +// This empty function exists so the Cache struct implements the Cache interface defined in cache/legacy.go +func (c *Cache) Close() error { + return nil +} + +func (c *Cache) Accounts() cache.AccountsService { + return c.accounts +} +func (c *Cache) Config() cache.ConfigService { + return c.config +} + +// AccountService handles the account information +type accountService struct { + shared *shared +} + +// Get will return Account from memory if it exists +func (s *accountService) Get(id string) (*cache.Account, error) { + if _, ok := s.shared.Accounts[id]; !ok { + return nil, fmt.Errorf("Not found") + } + return &cache.Account{ + ID: id, + }, nil +} + +// ConfigService not supported, always returns an error +type configService struct { + shared *shared +} + +// Get will return config from memory if it exists +func (s *configService) Get(id string) (string, error) { + cfg, ok := s.shared.Configs[id] + if !ok { + return "", fmt.Errorf("Not found") + } + return cfg, nil +} + +// Set not supported, always returns an error +func (s *configService) Set(id, value string) error { + return fmt.Errorf("Not supported") +} diff --git a/cache/filecache/filecache_test.go b/cache/filecache/filecache_test.go new file mode 100644 index 00000000000..80a72803bbe --- /dev/null +++ b/cache/filecache/filecache_test.go @@ -0,0 +1,79 @@ +package filecache + +import ( + "io/ioutil" + "os" + "testing" + + yaml "gopkg.in/yaml.v2" +) + +func TestFileCache(t *testing.T) { + fcf := fileCacheFile{ + Accounts: []string{"account1", "account2", "account3"}, + Configs: []fileConfig{ + { + ID: "one", + Config: "config1", + }, { + ID: "two", + Config: "config2", + }, { + ID: "three", + Config: "config3", + }, + }, + } + + bytes, err := yaml.Marshal(&fcf) + if err != nil { + t.Fatal(err) + } + + tmpfile, err := ioutil.TempFile("", "filecache") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) + + if _, err := tmpfile.Write(bytes); err != nil { + t.Fatal(err) + } + + if err := tmpfile.Close(); err != nil { + t.Fatal(err) + } + + dataCache, err := New(tmpfile.Name()) + if err != nil { + t.Fatal(err) + } + + a, err := dataCache.Accounts().Get("account1") + if err != nil { + t.Fatal(err) + } + + if a.ID != "account1" { + t.Error("fetched invalid account") + } + + a, err = dataCache.Accounts().Get("abc123") + if err == nil { + t.Error("account should not exist in cache") + } + + c, err := dataCache.Config().Get("one") + if err != nil { + t.Fatal(err) + } + + if c != "config1" { + t.Error("fetched invalid config") + } + + c, err = dataCache.Config().Get("abc123") + if err == nil { + t.Error("config should not exist in cache") + } +} diff --git a/cache/legacy.go b/cache/legacy.go new file mode 100644 index 00000000000..19c5ae5a4fe --- /dev/null +++ b/cache/legacy.go @@ -0,0 +1,33 @@ +package cache + +type Domain struct { + Domain string `json:"domain"` +} + +type App struct { + Bundle string `json:"bundle"` +} + +type Account struct { + ID string `json:"id"` + PriceGranularity string `json:"price_granularity"` +} + +type Configuration struct { + Type string `json:"type"` // required +} + +type Cache interface { + Close() error + Accounts() AccountsService + Config() ConfigService +} + +type AccountsService interface { + Get(string) (*Account, error) +} + +type ConfigService interface { + Get(string) (string, error) + Set(string, string) error +} diff --git a/cache/postgrescache/postgrescache.go b/cache/postgrescache/postgrescache.go new file mode 100644 index 00000000000..2333e08269e --- /dev/null +++ b/cache/postgrescache/postgrescache.go @@ -0,0 +1,139 @@ +package postgrescache + +import ( + "bytes" + "context" + "database/sql" + "encoding/gob" + "time" + + "github.com/prebid/prebid-server/stored_requests" + + "github.com/coocood/freecache" + "github.com/lib/pq" + "github.com/prebid/prebid-server/cache" +) + +type CacheConfig struct { + TTL int + Size int +} + +// shared configuration that get used by all of the services +type shared struct { + db *sql.DB + lru *freecache.Cache + ttlSeconds int +} + +// Cache postgres +type Cache struct { + shared *shared + accounts *accountService + config *configService +} + +// New creates new postgres.Cache +func New(db *sql.DB, cfg CacheConfig) *Cache { + shared := &shared{ + db: db, + lru: freecache.NewCache(cfg.Size), + ttlSeconds: cfg.TTL, + } + return &Cache{ + shared: shared, + accounts: &accountService{shared: shared}, + config: &configService{shared: shared}, + } +} + +func (c *Cache) Accounts() cache.AccountsService { + return c.accounts +} +func (c *Cache) Config() cache.ConfigService { + return c.config +} + +func (c *Cache) Close() error { + return c.shared.db.Close() +} + +// AccountService handles the account information +type accountService struct { + shared *shared +} + +// Get echos back the account +func (s *accountService) Get(key string) (*cache.Account, error) { + var account cache.Account + + b, err := s.shared.lru.Get([]byte(key)) + if err == nil { + return decodeAccount(b), nil + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50)*time.Millisecond) + defer cancel() + var id string + var priceGranularity sql.NullString + if err := s.shared.db.QueryRowContext(ctx, "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1", key).Scan(&id, &priceGranularity); err != nil { + /* TODO -- We should store failed attempts in the LRU as well to stop from hitting to DB */ + return nil, err + } + + account.ID = id + if priceGranularity.Valid { + account.PriceGranularity = priceGranularity.String + } + + buf := bytes.Buffer{} + if err := gob.NewEncoder(&buf).Encode(&account); err != nil { + panic(err) + } + + s.shared.lru.Set([]byte(key), buf.Bytes(), s.shared.ttlSeconds) + return &account, nil +} + +func decodeAccount(b []byte) *cache.Account { + var account cache.Account + buf := bytes.NewReader(b) + if err := gob.NewDecoder(buf).Decode(&account); err != nil { + panic(err) + } + return &account +} + +// ConfigService +type configService struct { + shared *shared +} + +func (s *configService) Set(id, value string) error { + return nil +} + +func (s *configService) Get(key string) (string, error) { + if b, err := s.shared.lru.Get([]byte(key)); err == nil { + return string(b), nil + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50)*time.Millisecond) + defer cancel() + var config string + if err := s.shared.db.QueryRowContext(ctx, "SELECT config FROM s2sconfig_config where uuid = $1 LIMIT 1", key).Scan(&config); err != nil { + // TODO -- We should store failed attempts in the LRU as well to stop from hitting to DB + + // If the user didn't give us a UUID, the query fails with this error. Wrap it so that we don't + // pollute the app logs with bad user input. + if pqErr, ok := err.(*pq.Error); ok && string(pqErr.Code) == "22P02" { + err = &stored_requests.NotFoundError{ + ID: key, + DataType: "Legacy Config", + } + } + return "", err + } + s.shared.lru.Set([]byte(key), []byte(config), s.shared.ttlSeconds) + return config, nil +} diff --git a/cache/postgrescache/postgrescache_test.go b/cache/postgrescache/postgrescache_test.go new file mode 100644 index 00000000000..bab96f1a11e --- /dev/null +++ b/cache/postgrescache/postgrescache_test.go @@ -0,0 +1,94 @@ +package postgrescache + +import ( + "database/sql" + "testing" + + "github.com/coocood/freecache" + "github.com/erikstmartin/go-testdb" +) + +type StubCache struct { + shared *shared + accounts *accountService + config *configService +} + +// New creates new postgres.Cache +func StubNew(cfg CacheConfig) *Cache { + shared := stubnewShared(cfg) + return &Cache{ + shared: shared, + accounts: &accountService{shared: shared}, + config: &configService{shared: shared}, + } +} + +func stubnewShared(conf CacheConfig) *shared { + db, _ := sql.Open("testdb", "") + + s := &shared{ + db: db, + lru: freecache.NewCache(conf.Size), + ttlSeconds: 0, + } + return s +} + +func TestPostgresDbPriceGranularity(t *testing.T) { + defer testdb.Reset() + + sql := "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1" + columns := []string{"uuid", "price_granularity"} + result := ` + bdc928ef-f725-4688-8171-c104cc715bdf,med + ` + testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result)) + + conf := CacheConfig{ + TTL: 3434, + Size: 100, + } + dataCache := StubNew(conf) + + account, err := dataCache.Accounts().Get("bdc928ef-f725-4688-8171-c104cc715bdf") + if err != nil { + t.Fatalf("test postgres db errored: %v", err) + } + + if account.ID != "bdc928ef-f725-4688-8171-c104cc715bdf" { + t.Error("Expected bdc928ef-f725-4688-8171-c104cc715bdf") + } + if account.PriceGranularity != "med" { + t.Error("Expected med") + } +} + +func TestPostgresDbNullPriceGranularity(t *testing.T) { + defer testdb.Reset() + + sql := "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1" + columns := []string{"uuid", "price_granularity"} + result := ` + bdc928ef-f725-4688-8171-c104cc715bdf + ` + testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result)) + + conf := CacheConfig{ + TTL: 3434, + Size: 100, + } + dataCache := StubNew(conf) + + account, err := dataCache.Accounts().Get("bdc928ef-f725-4688-8171-c104cc715bdf") + if err != nil { + t.Fatalf("test postgres db errored: %v", err) + } + + if account.ID != "bdc928ef-f725-4688-8171-c104cc715bdf" { + t.Error("Expected bdc928ef-f725-4688-8171-c104cc715bdf") + } + if account.PriceGranularity != "" { + t.Error("Expected null string") + } +} diff --git a/config/config.go b/config/config.go index 179a30af1cc..597ac1b41a7 100644 --- a/config/config.go +++ b/config/config.go @@ -33,6 +33,7 @@ type Configuration struct { RecaptchaSecret string `mapstructure:"recaptcha_secret"` HostCookie HostCookie `mapstructure:"host_cookie"` Metrics Metrics `mapstructure:"metrics"` + DataCache DataCache `mapstructure:"datacache"` StoredRequests StoredRequests `mapstructure:"stored_requests"` StoredRequestsAMP StoredRequests `mapstructure:"stored_amp_req"` CategoryMapping StoredRequests `mapstructure:"category_mapping"` @@ -84,6 +85,10 @@ type Configuration struct { GenerateBidID bool `mapstructure:"generate_bid_id"` // GenerateRequestID overrides the bidrequest.id in an AMP Request or an App Stored Request with a generated UUID if set to true. The default is false. GenerateRequestID bool `mapstructure:"generate_request_id"` + + // EnableLegacyAuction specifies if the original /auction endpoint with a custom PBS data model is allowed + // by the host. + EnableLegacyAuction bool `mapstructure:"enable_legacy_auction"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -396,6 +401,13 @@ func (m *PrometheusMetrics) Timeout() time.Duration { return time.Duration(m.TimeoutMillisRaw) * time.Millisecond } +type DataCache struct { + Type string `mapstructure:"type"` + Filename string `mapstructure:"filename"` + CacheSize int `mapstructure:"cache_size"` + TTLSeconds int `mapstructure:"ttl_seconds"` +} + // ExternalCache configures the externally accessible cache url. type ExternalCache struct { Scheme string `mapstructure:"scheme"` @@ -648,6 +660,10 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("metrics.prometheus.namespace", "") v.SetDefault("metrics.prometheus.subsystem", "") v.SetDefault("metrics.prometheus.timeout_ms", 10000) + v.SetDefault("datacache.type", "dummy") + v.SetDefault("datacache.filename", "") + v.SetDefault("datacache.cache_size", 0) + v.SetDefault("datacache.ttl_seconds", 0) v.SetDefault("category_mapping.filesystem.enabled", true) v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping") v.SetDefault("category_mapping.http.endpoint", "") @@ -938,6 +954,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("auto_gen_source_tid", true) v.SetDefault("generate_bid_id", false) v.SetDefault("generate_request_id", false) + v.SetDefault("enable_legacy_auction", false) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") diff --git a/config/config_test.go b/config/config_test.go index 4477d127f63..819eb21f819 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -130,6 +130,7 @@ func TestDefaults(t *testing.T) { cmpInts(t, "max_request_size", int(cfg.MaxRequestSize), 1024*256) cmpInts(t, "host_cookie.ttl_days", int(cfg.HostCookie.TTL), 90) cmpInts(t, "host_cookie.max_cookie_size_bytes", cfg.HostCookie.MaxCookieSizeBytes, 0) + cmpStrings(t, "datacache.type", cfg.DataCache.Type, "dummy") cmpStrings(t, "adapters.pubmatic.endpoint", cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint, "https://hbopenbid.pubmatic.com/translator?source=prebid-server") cmpInts(t, "currency_converter.fetch_interval_seconds", cfg.CurrencyConverter.FetchIntervalSeconds, 1800) cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") @@ -143,6 +144,7 @@ func TestDefaults(t *testing.T) { 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) + cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, false) //Assert purpose VendorExceptionMap hash tables were built correctly expectedTCF2 := TCF2{ @@ -314,6 +316,11 @@ metrics: account_adapter_details: true adapter_connections_metrics: true adapter_gdpr_request_blocked: true +datacache: + type: postgres + filename: /usr/db/db.db + cache_size: 10000000 + ttl_seconds: 3600 adapters: appnexus: endpoint: http://ib.adnxs.com/some/endpoint @@ -347,6 +354,7 @@ request_validation: ipv4_private_networks: ["1.1.1.0/24"] ipv6_private_networks: ["1111::/16", "2222::/16"] generate_bid_id: true +enable_legacy_auction: true `) var adapterExtraInfoConfig = []byte(` @@ -537,6 +545,10 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "metrics.influxdb.username", cfg.Metrics.Influxdb.Username, "admin") cmpStrings(t, "metrics.influxdb.password", cfg.Metrics.Influxdb.Password, "admin1324") cmpInts(t, "metrics.influxdb.metric_send_interval", cfg.Metrics.Influxdb.MetricSendInterval, 30) + cmpStrings(t, "datacache.type", cfg.DataCache.Type, "postgres") + cmpStrings(t, "datacache.filename", cfg.DataCache.Filename, "/usr/db/db.db") + cmpInts(t, "datacache.cache_size", cfg.DataCache.CacheSize, 10000000) + cmpInts(t, "datacache.ttl_seconds", cfg.DataCache.TTLSeconds, 3600) cmpStrings(t, "", cfg.CacheURL.GetBaseURL(), "http://prebidcache.net") cmpStrings(t, "", cfg.GetCachedAssetURL("a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11"), "http://prebidcache.net/cache?uuid=a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11") cmpStrings(t, "adapters.appnexus.endpoint", cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, "http://ib.adnxs.com/some/endpoint") @@ -567,6 +579,7 @@ func TestFullConfig(t *testing.T) { 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, "") + cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, true) } func TestUnmarshalAdapterExtraInfo(t *testing.T) { diff --git a/config/stored_requests.go b/config/stored_requests.go index e752e9e4d9d..ee78179eb65 100644 --- a/config/stored_requests.go +++ b/config/stored_requests.go @@ -133,6 +133,7 @@ func resolvedStoredRequestsConfig(cfg *Configuration) { cfg.StoredVideo.dataType = VideoDataType cfg.CategoryMapping.dataType = CategoryDataType cfg.Accounts.dataType = AccountDataType + return } func (cfg *StoredRequests) validate(errs []error) []error { diff --git a/config/structlog.go b/config/structlog.go index 911f475717d..a91e5ab857e 100644 --- a/config/structlog.go +++ b/config/structlog.go @@ -12,7 +12,7 @@ import ( type logMsg func(string, ...interface{}) var mapregex = regexp.MustCompile(`mapstructure:"([^"]+)"`) -var blocklistregexp = []*regexp.Regexp{ +var blacklistregexp = []*regexp.Regexp{ regexp.MustCompile("password"), } @@ -84,7 +84,7 @@ func fieldNameByTag(f reflect.StructField) string { } func allowedName(name string) bool { - for _, r := range blocklistregexp { + for _, r := range blacklistregexp { if r.MatchString(name) { return false } diff --git a/endpoints/auction.go b/endpoints/auction.go new file mode 100644 index 00000000000..10d2ced6c37 --- /dev/null +++ b/endpoints/auction.go @@ -0,0 +1,513 @@ +package endpoints + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "runtime/debug" + "sort" + "strconv" + "time" + + "github.com/golang/glog" + "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/cache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + pbc "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/privacy" + gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/usersync" +) + +var allSyncTypes []usersync.SyncType = []usersync.SyncType{usersync.SyncTypeIFrame, usersync.SyncTypeRedirect} + +type bidResult struct { + bidder *pbs.PBSBidder + bidList pbs.PBSBidSlice +} + +const defaultPriceGranularity = "med" + +func min(x, y int) int { + if x < y { + return x + } + return y +} + +func writeAuctionError(w http.ResponseWriter, s string, err error) { + var resp pbs.PBSResponse + if err != nil { + resp.Status = fmt.Sprintf("%s: %v", s, err) + } else { + resp.Status = s + } + b, err := json.Marshal(&resp) + if err != nil { + glog.Errorf("Failed to marshal auction error JSON: %s", err) + } else { + w.Write(b) + } +} + +type auction struct { + cfg *config.Configuration + syncersByBidder map[string]usersync.Syncer + gdprPerms gdpr.Permissions + metricsEngine metrics.MetricsEngine + dataCache cache.Cache + exchanges map[string]adapters.Adapter +} + +func Auction(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPerms gdpr.Permissions, metricsEngine metrics.MetricsEngine, dataCache cache.Cache, exchanges map[string]adapters.Adapter) httprouter.Handle { + a := &auction{ + cfg: cfg, + syncersByBidder: syncersByBidder, + gdprPerms: gdprPerms, + metricsEngine: metricsEngine, + dataCache: dataCache, + exchanges: exchanges, + } + return a.auction +} + +func (a *auction) auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + w.Header().Add("Content-Type", "application/json") + var labels = getDefaultLabels(r) + req, err := pbs.ParsePBSRequest(r, &a.cfg.AuctionTimeouts, a.dataCache, &(a.cfg.HostCookie)) + + defer a.recordMetrics(req, labels) + + if err != nil { + if glog.V(2) { + glog.Infof("Failed to parse /auction request: %v", err) + } + writeAuctionError(w, "Error parsing request", err) + labels.RequestStatus = metrics.RequestStatusBadInput + return + } + status := "OK" + setLabelSource(&labels, req, &status) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(req.TimeoutMillis)) + defer cancel() + account, err := a.dataCache.Accounts().Get(req.AccountID) + if err != nil { + if glog.V(2) { + glog.Infof("Invalid account id: %v", err) + } + writeAuctionError(w, "Unknown account id", fmt.Errorf("Unknown account")) + labels.RequestStatus = metrics.RequestStatusBadInput + return + } + labels.PubID = req.AccountID + resp := pbs.PBSResponse{ + Status: status, + TID: req.Tid, + BidderStatus: req.Bidders, + } + ch := make(chan bidResult) + sentBids := 0 + for _, bidder := range req.Bidders { + if ex, ok := a.exchanges[bidder.BidderCode]; ok { + // Make sure we have an independent label struct for each bidder. We don't want to run into issues with the goroutine below. + blabels := metrics.AdapterLabels{ + Source: labels.Source, + RType: labels.RType, + Adapter: openrtb_ext.BidderName(bidder.BidderCode), + PubID: labels.PubID, + CookieFlag: labels.CookieFlag, + AdapterBids: metrics.AdapterBidPresent, + } + if skip := a.processUserSync(req, bidder, blabels, ex, &ctx); skip == true { + continue + } + sentBids++ + bidderRunner := a.recoverSafely(func(bidder *pbs.PBSBidder, aLabels metrics.AdapterLabels) { + + start := time.Now() + bidList, err := ex.Call(ctx, req, bidder) + a.metricsEngine.RecordAdapterTime(aLabels, time.Since(start)) + bidder.ResponseTime = int(time.Since(start) / time.Millisecond) + processBidResult(bidList, bidder, &aLabels, a.metricsEngine, err) + + ch <- bidResult{ + bidder: bidder, + bidList: bidList, + // Bidder done, record bidder metrics + } + a.metricsEngine.RecordAdapterRequest(aLabels) + }) + + go bidderRunner(bidder, blabels) + + } else if bidder.BidderCode == "lifestreet" { + bidder.Error = "Bidder is no longer available" + } else { + bidder.Error = "Unsupported bidder" + } + } + for i := 0; i < sentBids; i++ { + result := <-ch + for _, bid := range result.bidList { + resp.Bids = append(resp.Bids, bid) + } + } + if err := cacheAccordingToMarkup(req, &resp, ctx, a, &labels); err != nil { + writeAuctionError(w, "Prebid cache failed", err) + labels.RequestStatus = metrics.RequestStatusErr + return + } + if req.SortBids == 1 { + sortBidsAddKeywordsMobile(resp.Bids, req, account.PriceGranularity) + } + if glog.V(2) { + glog.Infof("Request for %d ad units on url %s by account %s got %d bids", len(req.AdUnits), req.Url, req.AccountID, len(resp.Bids)) + } + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + enc.Encode(resp) +} + +func (a *auction) recoverSafely(inner func(*pbs.PBSBidder, metrics.AdapterLabels)) func(*pbs.PBSBidder, metrics.AdapterLabels) { + return func(bidder *pbs.PBSBidder, labels metrics.AdapterLabels) { + defer func() { + if r := recover(); r != nil { + if bidder == nil { + glog.Errorf("Legacy auction recovered panic: %v. Stack trace is: %v", r, string(debug.Stack())) + } else { + glog.Errorf("Legacy auction recovered panic from Bidder %s: %v. Stack trace is: %v", bidder.BidderCode, r, string(debug.Stack())) + } + a.metricsEngine.RecordAdapterPanic(labels) + } + }() + inner(bidder, labels) + } +} + +func (a *auction) shouldUsersync(ctx context.Context, bidder openrtb_ext.BidderName, gdprPrivacyPolicy gdprPrivacy.Policy) bool { + gdprSignal := gdpr.SignalAmbiguous + if signal, err := gdpr.SignalParse(gdprPrivacyPolicy.Signal); err != nil { + gdprSignal = signal + } + + if canSync, err := a.gdprPerms.HostCookiesAllowed(ctx, gdprSignal, gdprPrivacyPolicy.Consent); err != nil || !canSync { + return false + } + canSync, err := a.gdprPerms.BidderSyncAllowed(ctx, bidder, gdprSignal, gdprPrivacyPolicy.Consent) + return canSync && err == nil +} + +// cache video bids only for Web +func cacheVideoOnly(bids pbs.PBSBidSlice, ctx context.Context, deps *auction, labels *metrics.Labels) error { + var cobjs []*pbc.CacheObject + for _, bid := range bids { + if bid.CreativeMediaType == "video" { + cobjs = append(cobjs, &pbc.CacheObject{ + Value: bid.Adm, + IsVideo: true, + }) + } + } + err := pbc.Put(ctx, cobjs) + if err != nil { + return err + } + videoIndex := 0 + for _, bid := range bids { + if bid.CreativeMediaType == "video" { + bid.CacheID = cobjs[videoIndex].UUID + bid.CacheURL = deps.cfg.GetCachedAssetURL(bid.CacheID) + bid.NURL = "" + bid.Adm = "" + videoIndex++ + } + } + return nil +} + +// checkForValidBidSize goes through list of bids & find those which are banner mediaType and with height or width not defined +// determine the num of ad unit sizes that were used in corresponding bid request +// if num_adunit_sizes == 1, assign the height and/or width to bid's height/width +// if num_adunit_sizes > 1, reject the bid (remove from list) and return an error +// return updated bid list object for next steps in auction +func checkForValidBidSize(bids pbs.PBSBidSlice, bidder *pbs.PBSBidder) pbs.PBSBidSlice { + finalValidBids := make([]*pbs.PBSBid, len(bids)) + finalBidCounter := 0 +bidLoop: + for _, bid := range bids { + if isUndimensionedBanner(bid) { + for _, adunit := range bidder.AdUnits { + if copyBannerDimensions(&adunit, bid, finalValidBids, &finalBidCounter) { + continue bidLoop + } + } + } else { + finalValidBids[finalBidCounter] = bid + finalBidCounter = finalBidCounter + 1 + } + } + return finalValidBids[:finalBidCounter] +} + +func isUndimensionedBanner(bid *pbs.PBSBid) bool { + return bid.CreativeMediaType == "banner" && (bid.Height == 0 || bid.Width == 0) +} + +func copyBannerDimensions(adunit *pbs.PBSAdUnit, bid *pbs.PBSBid, finalValidBids []*pbs.PBSBid, finalBidCounter *int) bool { + var bidIDEqualsCode bool = false + + if adunit.BidID == bid.BidID && adunit.Code == bid.AdUnitCode && adunit.Sizes != nil { + if len(adunit.Sizes) == 1 { + bid.Width, bid.Height = adunit.Sizes[0].W, adunit.Sizes[0].H + finalValidBids[*finalBidCounter] = bid + *finalBidCounter += 1 + } else if len(adunit.Sizes) > 1 { + glog.Warningf("Bid was rejected for bidder %s because no size was defined", bid.BidderCode) + } + bidIDEqualsCode = true + } + + return bidIDEqualsCode +} + +// sortBidsAddKeywordsMobile sorts the bids and adds ad server targeting keywords to each bid. +// The bids are sorted by cpm to find the highest bid. +// The ad server targeting keywords are added to all bids, with specific keywords for the highest bid. +func sortBidsAddKeywordsMobile(bids pbs.PBSBidSlice, pbs_req *pbs.PBSRequest, priceGranularitySetting string) { + if priceGranularitySetting == "" { + priceGranularitySetting = defaultPriceGranularity + } + + // record bids by ad unit code for sorting + code_bids := make(map[string]pbs.PBSBidSlice, len(bids)) + for _, bid := range bids { + code_bids[bid.AdUnitCode] = append(code_bids[bid.AdUnitCode], bid) + } + + // loop through ad units to find top bid + for _, unit := range pbs_req.AdUnits { + bar := code_bids[unit.Code] + + if len(bar) == 0 { + if glog.V(3) { + glog.Infof("No bids for ad unit '%s'", unit.Code) + } + continue + } + sort.Sort(bar) + + // after sorting we need to add the ad targeting keywords + for i, bid := range bar { + // We should eventually check for the error and do something. + roundedCpm := exchange.GetPriceBucket(bid.Price, openrtb_ext.PriceGranularityFromString(priceGranularitySetting)) + + hbSize := "" + if bid.Width != 0 && bid.Height != 0 { + width := strconv.FormatInt(bid.Width, 10) + height := strconv.FormatInt(bid.Height, 10) + hbSize = width + "x" + height + } + + hbPbBidderKey := string(openrtb_ext.HbpbConstantKey) + "_" + bid.BidderCode + hbBidderBidderKey := string(openrtb_ext.HbBidderConstantKey) + "_" + bid.BidderCode + hbCacheIDBidderKey := string(openrtb_ext.HbCacheKey) + "_" + bid.BidderCode + hbDealIDBidderKey := string(openrtb_ext.HbDealIDConstantKey) + "_" + bid.BidderCode + hbSizeBidderKey := string(openrtb_ext.HbSizeConstantKey) + "_" + bid.BidderCode + if pbs_req.MaxKeyLength != 0 { + hbPbBidderKey = hbPbBidderKey[:min(len(hbPbBidderKey), int(pbs_req.MaxKeyLength))] + hbBidderBidderKey = hbBidderBidderKey[:min(len(hbBidderBidderKey), int(pbs_req.MaxKeyLength))] + hbCacheIDBidderKey = hbCacheIDBidderKey[:min(len(hbCacheIDBidderKey), int(pbs_req.MaxKeyLength))] + hbDealIDBidderKey = hbDealIDBidderKey[:min(len(hbDealIDBidderKey), int(pbs_req.MaxKeyLength))] + hbSizeBidderKey = hbSizeBidderKey[:min(len(hbSizeBidderKey), int(pbs_req.MaxKeyLength))] + } + + // fixes #288 where map was being overwritten instead of updated + if bid.AdServerTargeting == nil { + bid.AdServerTargeting = make(map[string]string) + } + kvs := bid.AdServerTargeting + + kvs[hbPbBidderKey] = roundedCpm + kvs[hbBidderBidderKey] = bid.BidderCode + kvs[hbCacheIDBidderKey] = bid.CacheID + + if hbSize != "" { + kvs[hbSizeBidderKey] = hbSize + } + if bid.DealId != "" { + kvs[hbDealIDBidderKey] = bid.DealId + } + // For the top bid, we want to add the following additional keys + if i == 0 { + kvs[string(openrtb_ext.HbpbConstantKey)] = roundedCpm + kvs[string(openrtb_ext.HbBidderConstantKey)] = bid.BidderCode + kvs[string(openrtb_ext.HbCacheKey)] = bid.CacheID + if bid.DealId != "" { + kvs[string(openrtb_ext.HbDealIDConstantKey)] = bid.DealId + } + if hbSize != "" { + kvs[string(openrtb_ext.HbSizeConstantKey)] = hbSize + } + } + } + } +} + +func getDefaultLabels(r *http.Request) metrics.Labels { + return metrics.Labels{ + Source: metrics.DemandUnknown, + RType: metrics.ReqTypeLegacy, + PubID: "", + CookieFlag: metrics.CookieFlagUnknown, + RequestStatus: metrics.RequestStatusOK, + } +} + +func setLabelSource(labels *metrics.Labels, req *pbs.PBSRequest, status *string) { + if req.App != nil { + labels.Source = metrics.DemandApp + } else { + labels.Source = metrics.DemandWeb + if req.Cookie.HasAnyLiveSyncs() { + labels.CookieFlag = metrics.CookieFlagYes + } else { + labels.CookieFlag = metrics.CookieFlagNo + *status = "no_cookie" + } + } +} + +func cacheAccordingToMarkup(req *pbs.PBSRequest, resp *pbs.PBSResponse, ctx context.Context, a *auction, labels *metrics.Labels) error { + if req.CacheMarkup == 1 { + cobjs := make([]*pbc.CacheObject, len(resp.Bids)) + for i, bid := range resp.Bids { + if bid.CreativeMediaType == "video" { + cobjs[i] = &pbc.CacheObject{ + Value: bid.Adm, + IsVideo: true, + } + } else { + cobjs[i] = &pbc.CacheObject{ + Value: &pbc.BidCache{ + Adm: bid.Adm, + NURL: bid.NURL, + Width: bid.Width, + Height: bid.Height, + }, + IsVideo: false, + } + } + } + if err := pbc.Put(ctx, cobjs); err != nil { + return err + } + for i, bid := range resp.Bids { + bid.CacheID = cobjs[i].UUID + bid.CacheURL = a.cfg.GetCachedAssetURL(bid.CacheID) + bid.NURL = "" + bid.Adm = "" + } + } else if req.CacheMarkup == 2 { + return cacheVideoOnly(resp.Bids, ctx, a, labels) + } + return nil +} + +func processBidResult(bidList pbs.PBSBidSlice, bidder *pbs.PBSBidder, aLabels *metrics.AdapterLabels, metricsEngine metrics.MetricsEngine, err error) { + if err != nil { + var s struct{} + if err == context.DeadlineExceeded { + aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorTimeout: s} + bidder.Error = "Timed out" + } else if err != context.Canceled { + bidder.Error = err.Error() + switch err.(type) { + case *errortypes.BadInput: + aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorBadInput: s} + case *errortypes.BadServerResponse: + aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorBadServerResponse: s} + default: + glog.Warningf("Error from bidder %v. Ignoring all bids: %v", bidder.BidderCode, err) + aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorUnknown: s} + } + } + } else if bidList != nil { + bidList = checkForValidBidSize(bidList, bidder) + bidder.NumBids = len(bidList) + for _, bid := range bidList { + var cpm = float64(bid.Price * 1000) + metricsEngine.RecordAdapterPrice(*aLabels, cpm) + switch bid.CreativeMediaType { + case "banner": + metricsEngine.RecordAdapterBidReceived(*aLabels, openrtb_ext.BidTypeBanner, bid.Adm != "") + case "video": + metricsEngine.RecordAdapterBidReceived(*aLabels, openrtb_ext.BidTypeVideo, bid.Adm != "") + } + bid.ResponseTime = bidder.ResponseTime + } + } else { + bidder.NoBid = true + aLabels.AdapterBids = metrics.AdapterBidNone + } +} + +func (a *auction) recordMetrics(req *pbs.PBSRequest, labels metrics.Labels) { + a.metricsEngine.RecordRequest(labels) + if req == nil { + a.metricsEngine.RecordLegacyImps(labels, 0) + return + } + a.metricsEngine.RecordLegacyImps(labels, len(req.AdUnits)) + a.metricsEngine.RecordRequestTime(labels, time.Since(req.Start)) +} + +func (a *auction) processUserSync(req *pbs.PBSRequest, bidder *pbs.PBSBidder, blabels metrics.AdapterLabels, ex adapters.Adapter, ctx *context.Context) bool { + var skip bool = false + if req.App != nil { + return skip + } + // If exchanges[bidderCode] exists, then a.syncers[bidderCode] exists *except for districtm*. + // OpenRTB handles aliases differently, so this hack will keep legacy code working. For all other + // bidderCodes, a.syncers[bidderCode] will exist if exchanges[bidderCode] also does. + // This is guaranteed by the TestSyncers unit test inside usersync/usersync_test.go, which compares these maps to the (source of truth) openrtb_ext.BidderMap: + syncerCode := bidder.BidderCode + if syncerCode == "districtm" { + syncerCode = "appnexus" + } + syncer := a.syncersByBidder[syncerCode] + uid, _, _ := req.Cookie.GetUID(syncer.Key()) + if uid == "" { + bidder.NoCookie = true + privacyPolicies := privacy.Policies{ + GDPR: gdprPrivacy.Policy{ + Signal: req.ParseGDPR(), + Consent: req.ParseConsent(), + }, + } + if a.shouldUsersync(*ctx, openrtb_ext.BidderName(syncerCode), privacyPolicies.GDPR) { + sync, err := syncer.GetSync(allSyncTypes, privacyPolicies) + if err == nil { + bidder.UsersyncInfo = &pbs.UsersyncInfo{ + URL: sync.URL, + Type: string(sync.Type), + SupportCORS: sync.SupportCORS, + } + } else { + glog.Errorf("Failed to get usersync info for %s: %v", syncerCode, err) + } + } + blabels.CookieFlag = metrics.CookieFlagNo + if ex.SkipNoCookies() { + skip = true + } + } + return skip +} diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go new file mode 100644 index 00000000000..1a30e025faa --- /dev/null +++ b/endpoints/auction_test.go @@ -0,0 +1,654 @@ +package endpoints + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/metrics" + metricsConf "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/prebid_cache_client" + gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/usersync" + "github.com/spf13/viper" + + "github.com/stretchr/testify/assert" +) + +func TestSortBidsAndAddKeywordsForMobile(t *testing.T) { + body := []byte(`{ + "max_key_length":20, + "user":{ + "gender":"F", + "buyeruid":"test_buyeruid", + "yob":2000, + "id":"testid" + }, + "prebid_version":"0.21.0-pre", + "sort_bids":1, + "ad_units":[ + { + "sizes":[ + { + "w":300, + "h":250 + } + ], + "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", + "code":"test_adunitcode" + } + ], + "cache_markup":1, + "app":{ + "bundle":"AppNexus.PrebidMobileDemo", + "ver":"0.0.1" + }, + "sdk":{ + "version":"0.0.1", + "platform":"iOS", + "source":"prebid-mobile" + }, + "device":{ + "ifa":"test_device_ifa", + "osv":"9.3.5", + "os":"iOS", + "make":"Apple", + "model":"iPhone6,1" + }, + "tid":"abcd", + "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" + } + `) + r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) + d, _ := dummycache.New() + hcc := config.HostCookie{} + + pbs_req, err := pbs.ParsePBSRequest(r, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, d, &hcc) + if err != nil { + t.Errorf("Unexpected error on parsing %v", err) + } + + bids := make(pbs.PBSBidSlice, 0) + + fb_bid := pbs.PBSBid{ + BidID: "test_bidid", + AdUnitCode: "test_adunitcode", + BidderCode: "audienceNetwork", + Price: 2.00, + Adm: "test_adm", + Width: 300, + Height: 250, + CacheID: "test_cache_id1", + DealId: "2345", + } + bids = append(bids, &fb_bid) + an_bid := pbs.PBSBid{ + BidID: "test_bidid2", + AdUnitCode: "test_adunitcode", + BidderCode: "appnexus", + Price: 1.00, + Adm: "test_adm", + Width: 320, + Height: 50, + CacheID: "test_cache_id2", + DealId: "1234", + } + bids = append(bids, &an_bid) + rb_bid := pbs.PBSBid{ + BidID: "test_bidid2", + AdUnitCode: "test_adunitcode", + BidderCode: "rubicon", + Price: 1.00, + Adm: "test_adm", + Width: 300, + Height: 250, + CacheID: "test_cache_id2", + DealId: "7890", + } + rb_bid.AdServerTargeting = map[string]string{ + "rpfl_1001": "15_tier0100", + } + bids = append(bids, &rb_bid) + nosize_bid := pbs.PBSBid{ + BidID: "test_bidid2", + AdUnitCode: "test_adunitcode", + BidderCode: "nosizebidder", + Price: 1.00, + Adm: "test_adm", + CacheID: "test_cache_id2", + } + bids = append(bids, &nosize_bid) + nodeal_bid := pbs.PBSBid{ + BidID: "test_bidid2", + AdUnitCode: "test_adunitcode", + BidderCode: "nodeal", + Price: 1.00, + Adm: "test_adm", + CacheID: "test_cache_id2", + } + bids = append(bids, &nodeal_bid) + pbs_resp := pbs.PBSResponse{ + Bids: bids, + } + sortBidsAddKeywordsMobile(pbs_resp.Bids, pbs_req, "") + + for _, bid := range bids { + if bid.AdServerTargeting == nil { + t.Error("Ad server targeting should not be nil") + } + if bid.BidderCode == "audienceNetwork" { + if bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)] != "300x250" { + t.Error(string(openrtb_ext.HbSizeConstantKey) + " key was not parsed correctly") + } + if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)] != "2.00" { + t.Error(string(openrtb_ext.HbpbConstantKey)+" key was not parsed correctly ", bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)]) + } + + if bid.AdServerTargeting[string(openrtb_ext.HbCacheKey)] != "test_cache_id1" { + t.Error(string(openrtb_ext.HbCacheKey) + " key was not parsed correctly") + } + if bid.AdServerTargeting[string(openrtb_ext.HbBidderConstantKey)] != "audienceNetwork" { + t.Error(string(openrtb_ext.HbBidderConstantKey) + " key was not parsed correctly") + } + if bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)] != "2345" { + t.Error(string(openrtb_ext.HbDealIDConstantKey) + " key was not parsed correctly ") + } + } + if bid.BidderCode == "appnexus" { + if bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)+"_appnexus"] != "320x50" { + t.Error(string(openrtb_ext.HbSizeConstantKey) + " key for appnexus bidder was not parsed correctly") + } + if bid.AdServerTargeting[string(openrtb_ext.HbCacheKey)+"_appnexus"] != "test_cache_id2" { + t.Error(string(openrtb_ext.HbCacheKey) + " key for appnexus bidder was not parsed correctly") + } + if bid.AdServerTargeting[string(openrtb_ext.HbBidderConstantKey)+"_appnexus"] != "appnexus" { + t.Error(string(openrtb_ext.HbBidderConstantKey) + " key for appnexus bidder was not parsed correctly") + } + if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)+"_appnexus"] != "1.00" { + t.Error(string(openrtb_ext.HbpbConstantKey) + " key for appnexus bidder was not parsed correctly") + } + if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)] != "" { + t.Error(string(openrtb_ext.HbpbConstantKey) + " key was parsed for two bidders") + } + if bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_appnexus"] != "1234" { + t.Errorf(string(openrtb_ext.HbDealIDConstantKey)+"_appnexus was not parsed correctly %v", bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_appnexus"]) + } + } + if bid.BidderCode == string(openrtb_ext.BidderRubicon) { + if bid.AdServerTargeting["rpfl_1001"] != "15_tier0100" { + t.Error("custom ad_server_targeting KVPs from adapter were not preserved") + } + } + if bid.BidderCode == "nosizebidder" { + if _, exists := bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)+"_nosizebidder"]; exists { + t.Error(string(openrtb_ext.HbSizeConstantKey)+" key for nosize bidder was not parsed correctly", bid.AdServerTargeting) + } + } + if bid.BidderCode == "nodeal" { + if _, exists := bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_nodeal"]; exists { + t.Error(string(openrtb_ext.HbDealIDConstantKey) + " key for nodeal bidder was not parsed correctly") + } + } + } +} + +var ( + MaxValueLength = 1024 * 10 + MaxNumValues = 10 +) + +type responseObject struct { + UUID string `json:"uuid"` +} + +type response struct { + Responses []responseObject `json:"responses"` +} + +type putAnyObject struct { + Type string `json:"type"` + Value json.RawMessage `json:"value"` +} + +type putAnyRequest struct { + Puts []putAnyObject `json:"puts"` +} + +func DummyPrebidCacheServer(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Failed to read the request body.", http.StatusBadRequest) + return + } + defer r.Body.Close() + var put putAnyRequest + + err = json.Unmarshal(body, &put) + if err != nil { + http.Error(w, "Request body "+string(body)+" is not valid JSON.", http.StatusBadRequest) + return + } + + if len(put.Puts) > MaxNumValues { + http.Error(w, fmt.Sprintf("More keys than allowed: %d", MaxNumValues), http.StatusBadRequest) + return + } + + resp := response{ + Responses: make([]responseObject, len(put.Puts)), + } + for i, p := range put.Puts { + resp.Responses[i].UUID = fmt.Sprintf("UUID-%d", i+1) // deterministic for testing + if len(p.Value) > MaxValueLength { + http.Error(w, fmt.Sprintf("Value is larger than allowed size: %d", MaxValueLength), http.StatusBadRequest) + return + } + if len(p.Value) == 0 { + http.Error(w, "Missing value.", http.StatusBadRequest) + return + } + if p.Type != "xml" && p.Type != "json" { + http.Error(w, fmt.Sprintf("Type must be one of [\"json\", \"xml\"]. Found %v", p.Type), http.StatusBadRequest) + return + } + } + + b, err := json.Marshal(&resp) + if err != nil { + http.Error(w, "Failed to serialize UUIDs into JSON.", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(b) +} + +func TestCacheVideoOnly(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer)) + defer server.Close() + + bids := make(pbs.PBSBidSlice, 0) + fbBid := pbs.PBSBid{ + BidID: "test_bidid0", + AdUnitCode: "test_adunitcode0", + BidderCode: "audienceNetwork", + Price: 2.00, + Adm: "fb_test_adm", + Width: 300, + Height: 250, + DealId: "2345", + CreativeMediaType: "video", + } + bids = append(bids, &fbBid) + anBid := pbs.PBSBid{ + BidID: "test_bidid1", + AdUnitCode: "test_adunitcode1", + BidderCode: "appnexus", + Price: 1.00, + Adm: "an_test_adm", + Width: 320, + Height: 50, + DealId: "1234", + CreativeMediaType: "banner", + } + bids = append(bids, &anBid) + rbBannerBid := pbs.PBSBid{ + BidID: "test_bidid2", + AdUnitCode: "test_adunitcode2", + BidderCode: "rubicon", + Price: 1.00, + Adm: "rb_banner_test_adm", + Width: 300, + Height: 250, + DealId: "7890", + CreativeMediaType: "banner", + } + bids = append(bids, &rbBannerBid) + rbVideoBid1 := pbs.PBSBid{ + BidID: "test_bidid3", + AdUnitCode: "test_adunitcode3", + BidderCode: "rubicon", + Price: 1.00, + Adm: "rb_video_test_adm1", + Width: 300, + Height: 250, + DealId: "7890", + CreativeMediaType: "video", + } + bids = append(bids, &rbVideoBid1) + rbVideoBid2 := pbs.PBSBid{ + BidID: "test_bidid4", + AdUnitCode: "test_adunitcode4", + BidderCode: "rubicon", + Price: 1.00, + Adm: "rb_video_test_adm2", + Width: 300, + Height: 250, + DealId: "7890", + CreativeMediaType: "video", + } + bids = append(bids, &rbVideoBid2) + + 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()) + } + syncersByBidder := map[string]usersync.Syncer{} + gdprPerms := gdpr.NewPermissions(context.Background(), config.GDPR{ + HostVendorID: 0, + }, nil, nil) + prebid_cache_client.InitPrebidCache(server.URL) + var labels = &metrics.Labels{} + if err := cacheVideoOnly(bids, ctx, &auction{cfg: cfg, syncersByBidder: syncersByBidder, gdprPerms: gdprPerms, metricsEngine: &metricsConf.NilMetricsEngine{}}, labels); err != nil { + t.Errorf("Prebid cache failed: %v \n", err) + return + } + if bids[0].CacheID != "UUID-1" { + t.Errorf("UUID was '%s', should have been 'UUID-1'", bids[0].CacheID) + } + if bids[1].CacheID != "" { + t.Errorf("UUID was '%s', should have been empty", bids[1].CacheID) + } + if bids[2].CacheID != "" { + t.Errorf("UUID was '%s', should have been empty", bids[2].CacheID) + } + if bids[3].CacheID != "UUID-2" { + t.Errorf("First object UUID was '%s', should have been 'UUID-2'", bids[3].CacheID) + } + if bids[4].CacheID != "UUID-3" { + t.Errorf("Second object UUID was '%s', should have been 'UUID-3'", bids[4].CacheID) + } +} + +func TestShouldUsersync(t *testing.T) { + tests := []struct { + description string + signal string + allowHostCookies bool + allowBidderSync bool + wantAllow bool + }{ + { + description: "Don't sync - GDPR on, host cookies disallows and bidder sync disallows", + signal: "1", + allowHostCookies: false, + allowBidderSync: false, + wantAllow: false, + }, + { + description: "Don't sync - GDPR on, host cookies disallows and bidder sync allows", + signal: "1", + allowHostCookies: false, + allowBidderSync: true, + wantAllow: false, + }, + { + description: "Don't sync - GDPR on, host cookies allows and bidder sync disallows", + signal: "1", + allowHostCookies: true, + allowBidderSync: false, + wantAllow: false, + }, + { + description: "Sync - GDPR on, host cookies allows and bidder sync allows", + signal: "1", + allowHostCookies: true, + allowBidderSync: true, + wantAllow: true, + }, + { + description: "Don't sync - invalid GDPR signal, host cookies disallows and bidder sync disallows", + signal: "2", + allowHostCookies: false, + allowBidderSync: false, + wantAllow: false, + }, + } + + for _, tt := range tests { + deps := auction{ + gdprPerms: &auctionMockPermissions{ + allowBidderSync: tt.allowBidderSync, + allowHostCookies: tt.allowHostCookies, + }, + } + gdprPrivacyPolicy := gdprPolicy.Policy{ + Signal: tt.signal, + } + + allow := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, gdprPrivacyPolicy) + assert.Equal(t, tt.wantAllow, allow, tt.description) + } +} + +type auctionMockPermissions struct { + allowBidderSync bool + allowHostCookies bool + allowBidRequest bool + passGeo bool + passID bool +} + +func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { + return m.allowHostCookies, nil +} + +func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { + return m.allowBidderSync, 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) { + bids := make(pbs.PBSBidSlice, 0) + // bid1 will be rejected due to undefined size when adunit has multiple sizes + bid1 := pbs.PBSBid{ + BidID: "test_bidid1", + AdUnitCode: "test_adunitcode1", + BidderCode: "randNetwork", + Price: 1.05, + Adm: "test_adm", + // Width: 100, + // Height: 100, + CreativeMediaType: "banner", + } + bids = append(bids, &bid1) + // bid2 will be considered a normal ideal banner bid + bid2 := pbs.PBSBid{ + BidID: "test_bidid2", + AdUnitCode: "test_adunitcode2", + BidderCode: "randNetwork", + Price: 1.05, + Adm: "test_adm", + Width: 100, + Height: 100, + CreativeMediaType: "banner", + } + bids = append(bids, &bid2) + // bid3 will have it's dimensions set based on sizes defined in request + bid3 := pbs.PBSBid{ + BidID: "test_bidid3", + AdUnitCode: "test_adunitcode3", + BidderCode: "randNetwork", + Price: 1.05, + Adm: "test_adm", + //Width: 200, + //Height: 200, + CreativeMediaType: "banner", + } + + bids = append(bids, &bid3) + + // bid4 will be ignored as it's a video creative type + bid4 := pbs.PBSBid{ + BidID: "test_bidid_video", + AdUnitCode: "test_adunitcode_video", + BidderCode: "randNetwork", + Price: 1.05, + Adm: "test_adm", + //Width: 400, + //Height: 400, + CreativeMediaType: "video", + } + + bids = append(bids, &bid4) + + mybidder := pbs.PBSBidder{ + BidderCode: "randNetwork", + AdUnitCode: "test_adunitcode", + AdUnits: []pbs.PBSAdUnit{ + { + BidID: "test_bidid1", + Sizes: []openrtb2.Format{ + { + W: 350, + H: 250, + }, + { + W: 300, + H: 50, + }, + }, + Code: "test_adunitcode1", + MediaTypes: []pbs.MediaType{ + pbs.MEDIA_TYPE_BANNER, + }, + }, + { + BidID: "test_bidid2", + Sizes: []openrtb2.Format{ + { + W: 100, + H: 100, + }, + }, + Code: "test_adunitcode2", + MediaTypes: []pbs.MediaType{ + pbs.MEDIA_TYPE_BANNER, + }, + }, + { + BidID: "test_bidid3", + Sizes: []openrtb2.Format{ + { + W: 200, + H: 200, + }, + }, + Code: "test_adunitcode3", + MediaTypes: []pbs.MediaType{ + pbs.MEDIA_TYPE_BANNER, + }, + }, + { + BidID: "test_bidid_video", + Sizes: []openrtb2.Format{ + { + W: 400, + H: 400, + }, + }, + Code: "test_adunitcode_video", + MediaTypes: []pbs.MediaType{ + pbs.MEDIA_TYPE_VIDEO, + }, + }, + { + BidID: "test_bidid3", + Sizes: []openrtb2.Format{ + { + W: 150, + H: 150, + }, + }, + Code: "test_adunitcode_x", + MediaTypes: []pbs.MediaType{ + pbs.MEDIA_TYPE_BANNER, + }, + }, + { + BidID: "test_bidid_y", + Sizes: []openrtb2.Format{ + { + W: 150, + H: 150, + }, + }, + Code: "test_adunitcode_3", + MediaTypes: []pbs.MediaType{ + pbs.MEDIA_TYPE_BANNER, + }, + }, + }, + } + + bids = checkForValidBidSize(bids, &mybidder) + + testdata, _ := json.MarshalIndent(bids, "", " ") + if len(bids) != 3 { + t.Errorf("Detected returned bid list did not contain only 3 bid objects as expected.\nBelow is the contents of the bid list\n%v", string(testdata)) + } + + for _, bid := range bids { + if bid.BidID == "test_bidid3" { + if bid.Width == 0 && bid.Height == 0 { + t.Errorf("Detected the Width & Height attributes in test bidID %v were not set to the dimensions used from the mybidder object", bid.BidID) + } + } + } +} + +func TestWriteAuctionError(t *testing.T) { + recorder := httptest.NewRecorder() + writeAuctionError(recorder, "some error message", nil) + var resp pbs.PBSResponse + json.Unmarshal(recorder.Body.Bytes(), &resp) + + if len(resp.Bids) != 0 { + t.Error("Error responses should return no bids.") + } + if resp.Status != "some error message" { + t.Errorf("The response status should be the error message. Got: %s", resp.Status) + } + + if len(resp.BidderStatus) != 0 { + t.Errorf("Error responses shouldn't have any BidderStatus elements. Got %d", len(resp.BidderStatus)) + } +} + +func TestPanicRecovery(t *testing.T) { + testAuction := auction{ + cfg: nil, + syncersByBidder: nil, + gdprPerms: &auctionMockPermissions{ + allowBidderSync: false, + allowHostCookies: false, + }, + metricsEngine: &metricsConf.NilMetricsEngine{}, + } + panicker := func(bidder *pbs.PBSBidder, blables metrics.AdapterLabels) { + panic("panic!") + } + recovered := testAuction.recoverSafely(panicker) + recovered(nil, metrics.AdapterLabels{}) +} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index eb6f17f2359..790a8d6482d 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1572,6 +1572,15 @@ func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) } } +// parseUserID gets this user's ID for the host machine, if it exists. +func parseUserID(cfg *config.Configuration, httpReq *http.Request) (string, bool) { + if hostCookie, err := httpReq.Cookie(cfg.HostCookie.CookieName); hostCookie != nil && err == nil { + return hostCookie.Value, true + } else { + return "", false + } +} + // Write(return) errors to the client, if any. Returns true if errors were found. func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) bool { var rc bool = false diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 20fdd56e74b..a259719ba8a 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -25,6 +25,7 @@ import ( "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "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" @@ -3724,6 +3725,21 @@ func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []s return testStoredRequestData, testStoredImpData, nil } +var mockAccountData = map[string]json.RawMessage{ + "valid_acct": json.RawMessage(`{"disabled":false}`), +} + +type mockAccountFetcher struct { +} + +func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { + if account, ok := mockAccountData[accountID]; ok { + return account, nil + } else { + return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} + } +} + type mockExchange struct { lastRequest *openrtb2.BidRequest } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index b7eb0b27c0b..3163cd9d323 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1435,3 +1435,19 @@ var testVideoStoredImpData = map[string]json.RawMessage{ var testVideoStoredRequestData = map[string]json.RawMessage{ "80ce30c53c16e6ede735f123ef6e32361bfc7b22": json.RawMessage(`{"accountid": "11223344", "site": {"page": "mygame.foo.com"}}`), } + +func loadValidRequest(t *testing.T) *openrtb_ext.BidRequestVideo { + reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json") + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + + reqBody := getRequestPayload(t, reqData) + + reqVideo := &openrtb_ext.BidRequestVideo{} + if err := json.Unmarshal(reqBody, reqVideo); err != nil { + t.Fatalf("Failed to unmarshal the request: %v", err) + } + + return reqVideo +} diff --git a/exchange/auction.go b/exchange/auction.go index c8aff684e41..94e808801d9 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -311,6 +311,13 @@ func valOrZero(useVal bool, val int) int { return 0 } +func maybeMake(shouldMake bool, capacity int) []prebid_cache_client.Cacheable { + if shouldMake { + return make([]prebid_cache_client.Cacheable, 0, capacity) + } + return nil +} + func cacheTTL(impTTL int64, bidTTL int64, defTTL int64, buffer int64) (ttl int64) { if impTTL <= 0 && bidTTL <= 0 { // Only use default if there is no imp nor bid TTL provided. We don't want the default diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 455ae5018e8..ee064fcb6f1 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -555,6 +555,12 @@ type pbsBid struct { Bidder openrtb_ext.BidderName `json:"bidder"` } +type cacheComparator struct { + freq int + expectedKeys []string + actualKeys []string +} + type mockCache struct { scheme string host string diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 82f058514f7..da31658e32d 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -1795,6 +1795,7 @@ func (bidder *mixedMultiBidder) MakeBids(internalRequest *openrtb2.BidRequest, e } type bidRejector struct { + httpRequest *adapters.RequestData httpResponse *adapters.ResponseData } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index b4fd270d023..9dcf9d66a7f 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2997,7 +2997,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, _, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &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") diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 48e054c7bda..8991a116624 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,14 +8,17 @@ import ( "testing" "time" - "github.com/prebid/prebid-server/adapters" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/gdpr" + + metricsConf "github.com/prebid/prebid-server/metrics/config" metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -85,7 +88,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op ex := &exchange{ adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), - me: &metricsConfig.NilMetricsEngine{}, + me: &metricsConf.NilMetricsEngine{}, cache: &wellBehavedCache{}, cacheTime: time.Duration(0), gDPR: gdpr.AlwaysAllow{}, @@ -126,6 +129,14 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op return buildBidMap(bidResp.SeatBid, len(mockBids)) } +func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb2.Bid) []openrtb_ext.BidderName { + bidders := make([]openrtb_ext.BidderName, 0, len(bids)) + for name := range bids { + bidders = append(bidders, name) + } + return bidders +} + func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]adaptedBidder { adapterMap := make(map[openrtb_ext.BidderName]adaptedBidder, len(bids)) for bidder, bids := range bids { diff --git a/go.mod b/go.mod index e673d2218c7..347f36bcf86 100644 --- a/go.mod +++ b/go.mod @@ -8,17 +8,20 @@ require ( github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect + github.com/blang/semver v3.5.1+incompatible 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 github.com/docker/go-units v0.4.0 + github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/influxdata/influxdb v1.6.1 github.com/julienschmidt/httprouter v1.1.0 github.com/lib/pq v1.0.0 + github.com/magiconair/properties v1.8.5 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 diff --git a/go.sum b/go.sum index bcab99e4ebf..31e6b8fc93f 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -85,6 +87,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= diff --git a/main.go b/main.go index 76fa64f77ef..6087c3d69dd 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" + pbc "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/router" "github.com/prebid/prebid-server/server" "github.com/prebid/prebid-server/util/task" @@ -55,6 +56,8 @@ func serve(cfg *config.Configuration) error { return err } + pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL()) + corsRouter := router.SupportCORS(r) server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(currencyConverter, fetchingInterval), r.MetricsEngine) diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 66849db5864..51ba8cafe2f 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -90,6 +90,13 @@ func (me *MultiMetricsEngine) RecordImps(implabels metrics.ImpLabels) { } } +// RecordImps for the legacy endpoint +func (me *MultiMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { + for _, thisME := range *me { + thisME.RecordLegacyImps(labels, numImps) + } +} + // RecordRequestTime across all engines func (me *MultiMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { for _, thisME := range *me { @@ -271,6 +278,10 @@ func (me *NilMetricsEngine) RecordConnectionClose(success bool) { func (me *NilMetricsEngine) RecordImps(implabels metrics.ImpLabels) { } +// RecordLegacyImps as a noop +func (me *NilMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { +} + // RecordRequestTime as a noop func (me *NilMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { } diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index e4227afda77..0d6bcdb922d 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -78,6 +78,7 @@ func TestMultiMetricsEngine(t *testing.T) { for i := 0; i < 5; i++ { metricsEngine.RecordRequest(labels) metricsEngine.RecordImps(impTypeLabels) + metricsEngine.RecordLegacyImps(labels, 2) metricsEngine.RecordRequestTime(labels, time.Millisecond*20) metricsEngine.RecordAdapterRequest(pubLabels) metricsEngine.RecordAdapterRequest(apnLabels) @@ -146,6 +147,7 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "Request", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusOK].Count(), 5) VerifyMetrics(t, "ImpMeter", goEngine.ImpMeter.Count(), 8) + VerifyMetrics(t, "LegacyImpMeter", goEngine.LegacyImpMeter.Count(), 10) VerifyMetrics(t, "NoCookieMeter", goEngine.NoCookieMeter.Count(), 0) VerifyMetrics(t, "AdapterMetrics.Pubmatic.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].GotBidsMeter.Count(), 5) VerifyMetrics(t, "AdapterMetrics.Pubmatic.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].NoBidMeter.Count(), 0) diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index c93f10602c7..615b83b8be9 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -18,6 +18,7 @@ type Metrics struct { ConnectionAcceptErrorMeter metrics.Meter ConnectionCloseErrorMeter metrics.Meter ImpMeter metrics.Meter + LegacyImpMeter metrics.Meter AppRequestMeter metrics.Meter NoCookieMeter metrics.Meter RequestTimer metrics.Timer @@ -100,6 +101,9 @@ type accountMetrics struct { adapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics } +// Defining an "unknown" bidder +const unknownBidder openrtb_ext.BidderName = "unknown" + // NewBlankMetrics creates a new Metrics object with all blank metrics object. This may also be useful for // testing routines to ensure that no metrics are written anywhere. // @@ -118,6 +122,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa ConnectionAcceptErrorMeter: blankMeter, ConnectionCloseErrorMeter: blankMeter, ImpMeter: blankMeter, + LegacyImpMeter: blankMeter, AppRequestMeter: blankMeter, NoCookieMeter: blankMeter, RequestTimer: blankTimer, @@ -211,6 +216,7 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.ConnectionAcceptErrorMeter = metrics.GetOrRegisterMeter("connection_accept_errors", registry) newMetrics.ConnectionCloseErrorMeter = metrics.GetOrRegisterMeter("connection_close_errors", registry) newMetrics.ImpMeter = metrics.GetOrRegisterMeter("imps_requested", registry) + newMetrics.LegacyImpMeter = metrics.GetOrRegisterMeter("legacy_imps_requested", registry) newMetrics.ImpsTypeBanner = metrics.GetOrRegisterMeter("imp_banner", registry) newMetrics.ImpsTypeVideo = metrics.GetOrRegisterMeter("imp_video", registry) @@ -446,6 +452,10 @@ func (me *Metrics) RecordImps(labels ImpLabels) { } } +func (me *Metrics) RecordLegacyImps(labels Labels, numImps int) { + me.LegacyImpMeter.Mark(int64(numImps)) +} + func (me *Metrics) RecordConnectionAccept(success bool) { if success { me.ConnectionCounter.Inc(1) diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index dd2430d6b74..346a64a737f 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -36,6 +36,10 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "prebid_cache_request_time.ok", m.PrebidCacheRequestTimerSuccess) ensureContains(t, registry, "prebid_cache_request_time.err", m.PrebidCacheRequestTimerError) + ensureContains(t, registry, "requests.ok.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusOK]) + ensureContains(t, registry, "requests.badinput.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusBadInput]) + ensureContains(t, registry, "requests.err.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusErr]) + ensureContains(t, registry, "requests.networkerr.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusNetworkErr]) ensureContains(t, registry, "requests.ok.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusOK]) ensureContains(t, registry, "requests.badinput.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusBadInput]) ensureContains(t, registry, "requests.err.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusErr]) diff --git a/metrics/metrics.go b/metrics/metrics.go index 9d16143f0d4..af45f9b4f5a 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -382,6 +382,7 @@ type MetricsEngine interface { RecordConnectionClose(success bool) RecordRequest(labels Labels) // ignores adapter. only statusOk and statusErr fom status RecordImps(labels ImpLabels) // RecordImps across openRTB2 engines that support the 'Native' Imp Type + RecordLegacyImps(labels Labels, numImps int) // RecordImps for the legacy engine RecordRequestTime(labels Labels, length time.Duration) // ignores adapter. only statusOk and statusErr fom status RecordAdapterRequest(labels AdapterLabels) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index c8d5311c3a4..b8ab23b768a 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -32,6 +32,11 @@ func (me *MetricsEngineMock) RecordImps(labels ImpLabels) { me.Called(labels) } +// RecordLegacyImps mock +func (me *MetricsEngineMock) RecordLegacyImps(labels Labels, numImps int) { + me.Called(labels, numImps) +} + // RecordRequestTime mock func (me *MetricsEngineMock) RecordRequestTime(labels Labels, length time.Duration) { me.Called(labels, length) diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 53a5afabd53..52470369094 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -476,6 +476,10 @@ func (m *Metrics) RecordImps(labels metrics.ImpLabels) { }).Inc() } +func (m *Metrics) RecordLegacyImps(labels metrics.Labels, numImps int) { + m.impressionsLegacy.Add(float64(numImps)) +} + func (m *Metrics) RecordRequestTime(labels metrics.Labels, length time.Duration) { if labels.RequestStatus == metrics.RequestStatusOK { m.requestsTimer.With(prometheus.Labels{ diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index fc8abdb9d04..0fe852b81df 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -355,6 +355,16 @@ func TestImpressionsMetric(t *testing.T) { } } +func TestLegacyImpressionsMetric(t *testing.T) { + m := createMetricsForTesting() + + m.RecordLegacyImps(metrics.Labels{}, 42) + + expectedCount := float64(42) + assertCounterValue(t, "", "impressionsLegacy", m.impressionsLegacy, + expectedCount) +} + func TestRequestTimeMetric(t *testing.T) { requestType := metrics.ReqTypeORTB2Web performTest := func(m *Metrics, requestStatus metrics.RequestStatus, timeInMs float64) { @@ -1152,6 +1162,18 @@ func TestPrebidCacheRequestTimeMetric(t *testing.T) { assertHistogram(t, "Error", errorResult, errorExpectedCount, errorExpectedSum) } +func TestMetricAccumulationSpotCheck(t *testing.T) { + m := createMetricsForTesting() + + m.RecordLegacyImps(metrics.Labels{}, 1) + m.RecordLegacyImps(metrics.Labels{}, 2) + m.RecordLegacyImps(metrics.Labels{}, 3) + + expectedValue := float64(1 + 2 + 3) + assertCounterValue(t, "", "impressionsLegacy", m.impressionsLegacy, + expectedValue) +} + func TestRecordRequestQueueTimeMetric(t *testing.T) { performTest := func(m *Metrics, requestStatus bool, requestType metrics.RequestType, timeInSec float64) { m.RecordRequestQueueTime(requestStatus, requestType, time.Duration(timeInSec*float64(time.Second))) diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index d99f2f6f39b..4e2d1ff5ba1 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -104,6 +104,15 @@ func setUidStatusesAsString() []string { return valuesAsString } +func storedDataTypesAsString() []string { + values := metrics.StoredDataTypes() + valuesAsString := make([]string, len(values)) + for i, v := range values { + valuesAsString[i] = string(v) + } + return valuesAsString +} + func storedDataFetchTypesAsString() []string { values := metrics.StoredDataFetchTypes() valuesAsString := make([]string, len(values)) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 14a261e314e..7976451877c 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -12,6 +12,8 @@ import ( "github.com/xeipuuv/gojsonschema" ) +const schemaDirectory = "static/bidder-params" + // BidderName refers to a core bidder id or an alias id. type BidderName string diff --git a/openrtb_ext/imp_beachfront.go b/openrtb_ext/imp_beachfront.go index 9e65d54b82b..104ca8f9fb7 100644 --- a/openrtb_ext/imp_beachfront.go +++ b/openrtb_ext/imp_beachfront.go @@ -4,10 +4,10 @@ type ExtImpBeachfront struct { AppId string `json:"appId"` AppIds ExtImpBeachfrontAppIds `json:"appIds"` BidFloor float64 `json:"bidfloor"` - VideoResponseType string `json:"videoResponseType,omitempty"` + VideoResponseType string `json:"videoResponseType, omitempty"` } type ExtImpBeachfrontAppIds struct { - Video string `json:"video,omitempty"` - Banner string `json:"banner,omitempty"` + Video string `json:"video, omitempty"` + Banner string `json:"banner, omitempty"` } diff --git a/openrtb_ext/imp_nanointeractive.go b/openrtb_ext/imp_nanointeractive.go index 77e386237ac..28db5be0d07 100644 --- a/openrtb_ext/imp_nanointeractive.go +++ b/openrtb_ext/imp_nanointeractive.go @@ -3,8 +3,8 @@ package openrtb_ext // ExtImpNanoInteractive defines the contract for bidrequest.imp[i].ext.nanointeractive type ExtImpNanoInteractive struct { Pid string `json:"pid"` - Nq []string `json:"nq,omitempty"` - Category string `json:"category,omitempty"` - SubId string `json:"subId,omitempty"` - Ref string `json:"ref,omitempty"` + Nq []string `json:"nq, omitempty"` + Category string `json:"category, omitempty"` + SubId string `json:"subId, omitempty"` + Ref string `json:"ref, omitempty"` } diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go new file mode 100644 index 00000000000..c05b9a8c00d --- /dev/null +++ b/pbs/pbsrequest.go @@ -0,0 +1,403 @@ +package pbs + +import ( + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/prebid/prebid-server/cache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/httputil" + "github.com/prebid/prebid-server/util/iputil" + + "github.com/blang/semver" + "github.com/buger/jsonparser" + "github.com/golang/glog" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "golang.org/x/net/publicsuffix" +) + +const MAX_BIDDERS = 8 + +type MediaType byte + +const ( + MEDIA_TYPE_BANNER MediaType = iota + MEDIA_TYPE_VIDEO +) + +type ConfigCache interface { + LoadConfig(string) ([]Bids, error) +} + +type Bids struct { + BidderCode string `json:"bidder"` + BidID string `json:"bid_id"` + Params json.RawMessage `json:"params"` +} + +// Structure for holding video-specific information +type PBSVideo struct { + //Content MIME types supported. Popular MIME types may include “video/x-ms-wmv” for Windows Media and “video/x-flv” for Flash Video. + Mimes []string `json:"mimes,omitempty"` + + //Minimum video ad duration in seconds. + Minduration int64 `json:"minduration,omitempty"` + + // Maximum video ad duration in seconds. + Maxduration int64 `json:"maxduration,omitempty"` + + //Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. + Startdelay int64 `json:"startdelay,omitempty"` + + // Indicates if the player will allow the video to be skipped ( 0 = no, 1 = yes). + Skippable int `json:"skippable,omitempty"` + + // Playback method code Description + // 1 - Initiates on Page Load with Sound On + // 2 - Initiates on Page Load with Sound Off by Default + // 3 - Initiates on Click with Sound On + // 4 - Initiates on Mouse-Over with Sound On + // 5 - Initiates on Entering Viewport with Sound On + // 6 - Initiates on Entering Viewport with Sound Off by Default + PlaybackMethod int8 `json:"playback_method,omitempty"` + + //protocols as specified in ORTB 5.8 + // 1 VAST 1.0 + // 2 VAST 2.0 + // 3 VAST 3.0 + // 4 VAST 1.0 Wrapper + // 5 VAST 2.0 Wrapper + // 6 VAST 3.0 Wrapper + // 7 VAST 4.0 + // 8 VAST 4.0 Wrapper + // 9 DAAST 1.0 + // 10 DAAST 1.0 Wrapper + Protocols []int8 `json:"protocols,omitempty"` +} + +type AdUnit struct { + Code string `json:"code"` + TopFrame int8 `json:"is_top_frame"` + Sizes []openrtb2.Format `json:"sizes"` + Bids []Bids `json:"bids"` + ConfigID string `json:"config_id"` + MediaTypes []string `json:"media_types"` + Instl int8 `json:"instl"` + Video PBSVideo `json:"video"` +} + +type PBSAdUnit struct { + Sizes []openrtb2.Format + TopFrame int8 + Code string + BidID string + Params json.RawMessage + Video PBSVideo + MediaTypes []MediaType + Instl int8 +} + +func ParseMediaType(s string) (MediaType, error) { + mediaTypes := map[string]MediaType{"BANNER": MEDIA_TYPE_BANNER, "VIDEO": MEDIA_TYPE_VIDEO} + t, ok := mediaTypes[strings.ToUpper(s)] + if !ok { + return 0, fmt.Errorf("Invalid MediaType %s", s) + } + return t, nil +} + +type SDK struct { + Version string `json:"version"` + Source string `json:"source"` + Platform string `json:"platform"` +} + +type PBSBidder struct { + BidderCode string `json:"bidder"` + AdUnitCode string `json:"ad_unit,omitempty"` // for index to dedup responses + ResponseTime int `json:"response_time_ms,omitempty"` + NumBids int `json:"num_bids,omitempty"` + Error string `json:"error,omitempty"` + NoCookie bool `json:"no_cookie,omitempty"` + NoBid bool `json:"no_bid,omitempty"` + UsersyncInfo *UsersyncInfo `json:"usersync,omitempty"` + Debug []*BidderDebug `json:"debug,omitempty"` + + AdUnits []PBSAdUnit `json:"-"` +} + +type UsersyncInfo struct { + URL string `json:"url,omitempty"` + Type string `json:"type,omitempty"` + SupportCORS bool `json:"supportCORS,omitempty"` +} + +func (bidder *PBSBidder) LookupBidID(Code string) string { + for _, unit := range bidder.AdUnits { + if unit.Code == Code { + return unit.BidID + } + } + return "" +} + +func (bidder *PBSBidder) LookupAdUnit(Code string) (unit *PBSAdUnit) { + for _, unit := range bidder.AdUnits { + if unit.Code == Code { + return &unit + } + } + return nil +} + +type PBSRequest struct { + AccountID string `json:"account_id"` + Tid string `json:"tid"` + CacheMarkup int8 `json:"cache_markup"` + SortBids int8 `json:"sort_bids"` + MaxKeyLength int8 `json:"max_key_length"` + Secure int8 `json:"secure"` + TimeoutMillis int64 `json:"timeout_millis"` + AdUnits []AdUnit `json:"ad_units"` + IsDebug bool `json:"is_debug"` + App *openrtb2.App `json:"app"` + Device *openrtb2.Device `json:"device"` + PBSUser json.RawMessage `json:"user"` + SDK *SDK `json:"sdk"` + + // internal + Bidders []*PBSBidder `json:"-"` + User *openrtb2.User `json:"-"` + Cookie *usersync.Cookie `json:"-"` + Url string `json:"-"` + Domain string `json:"-"` + Regs *openrtb2.Regs `json:"-"` + Start time.Time +} + +func ConfigGet(cache cache.Cache, id string) ([]Bids, error) { + conf, err := cache.Config().Get(id) + if err != nil { + return nil, err + } + + bids := make([]Bids, 0) + err = json.Unmarshal([]byte(conf), &bids) + if err != nil { + return nil, err + } + + return bids, nil +} + +func ParseMediaTypes(types []string) []MediaType { + var mtypes []MediaType + mtmap := make(map[MediaType]bool) + + if types == nil { + mtypes = append(mtypes, MEDIA_TYPE_BANNER) + } else { + for _, t := range types { + mt, er := ParseMediaType(t) + if er != nil { + glog.Infof("Invalid media type: %s", er) + } else { + if !mtmap[mt] { + mtypes = append(mtypes, mt) + mtmap[mt] = true + } + } + } + if len(mtypes) == 0 { + mtypes = append(mtypes, MEDIA_TYPE_BANNER) + } + } + return mtypes +} + +var ipv4Validator iputil.IPValidator = iputil.VersionIPValidator{Version: iputil.IPv4} + +func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.Cache, hostCookieConfig *config.HostCookie) (*PBSRequest, error) { + defer r.Body.Close() + + pbsReq := &PBSRequest{} + err := json.NewDecoder(r.Body).Decode(&pbsReq) + if err != nil { + return nil, err + } + pbsReq.Start = time.Now() + + if len(pbsReq.AdUnits) == 0 { + return nil, fmt.Errorf("No ad units specified") + } + + pbsReq.TimeoutMillis = int64(cfg.LimitAuctionTimeout(time.Duration(pbsReq.TimeoutMillis)*time.Millisecond) / time.Millisecond) + + if pbsReq.Device == nil { + pbsReq.Device = &openrtb2.Device{} + } + if ip, _ := httputil.FindIP(r, ipv4Validator); ip != nil { + pbsReq.Device.IP = ip.String() + } + + if pbsReq.SDK == nil { + pbsReq.SDK = &SDK{} + } + + // Early versions of prebid mobile are sending requests with gender indicated by numbers, + // those traffic can't be parsed by latest Prebid Server after the change of gender to use string so clients using early versions can't be monetized. + // To handle those traffic, adding a check here to ignore the sent gender for versions lower than 0.0.2. + v1, err := semver.Make(pbsReq.SDK.Version) + v2, err := semver.Make("0.0.2") + if v1.Compare(v2) >= 0 && pbsReq.PBSUser != nil { + err = json.Unmarshal([]byte(pbsReq.PBSUser), &pbsReq.User) + if err != nil { + return nil, err + } + } + + if pbsReq.User == nil { + pbsReq.User = &openrtb2.User{} + } + + // use client-side data for web requests + if pbsReq.App == nil { + pbsReq.Cookie = usersync.ParseCookieFromRequest(r, hostCookieConfig) + + pbsReq.Device.UA = r.Header.Get("User-Agent") + + pbsReq.Url = r.Header.Get("Referer") // must be specified in the header + // TODO: this should explicitly put us in test mode + if r.FormValue("url_override") != "" { + pbsReq.Url = r.FormValue("url_override") + } + if strings.Index(pbsReq.Url, "http") == -1 { + pbsReq.Url = fmt.Sprintf("http://%s", pbsReq.Url) + } + + url, err := url.Parse(pbsReq.Url) + if err != nil { + return nil, fmt.Errorf("Invalid URL '%s': %v", pbsReq.Url, err) + } + + if url.Host == "" { + return nil, fmt.Errorf("Host not found from URL '%v'", url) + } + + pbsReq.Domain, err = publicsuffix.EffectiveTLDPlusOne(url.Host) + if err != nil { + return nil, fmt.Errorf("Invalid URL '%s': %v", url.Host, err) + } + } + + if r.FormValue("debug") == "1" { + pbsReq.IsDebug = true + } + + if httputil.IsSecure(r) { + pbsReq.Secure = 1 + } + + pbsReq.Bidders = make([]*PBSBidder, 0, MAX_BIDDERS) + + for _, unit := range pbsReq.AdUnits { + bidders := unit.Bids + if unit.ConfigID != "" { + bidders, err = ConfigGet(cache, unit.ConfigID) + if err != nil { + if _, notFound := err.(*stored_requests.NotFoundError); !notFound { + glog.Warningf("Failed to load config '%s' from cache: %v", unit.ConfigID, err) + } + // proceed with other ad units + continue + } + } + + if glog.V(2) { + glog.Infof("Ad unit %s has %d bidders for %d sizes", unit.Code, len(bidders), len(unit.Sizes)) + } + + mtypes := ParseMediaTypes(unit.MediaTypes) + for _, b := range bidders { + var bidder *PBSBidder + for _, pb := range pbsReq.Bidders { + if pb.BidderCode == b.BidderCode { + bidder = pb + } + } + + if bidder == nil { + bidder = &PBSBidder{BidderCode: b.BidderCode} + pbsReq.Bidders = append(pbsReq.Bidders, bidder) + } + if b.BidID == "" { + b.BidID = fmt.Sprintf("%d", rand.Int63()) + } + + pau := PBSAdUnit{ + Sizes: unit.Sizes, + TopFrame: unit.TopFrame, + Code: unit.Code, + Instl: unit.Instl, + Params: b.Params, + BidID: b.BidID, + MediaTypes: mtypes, + Video: unit.Video, + } + + bidder.AdUnits = append(bidder.AdUnits, pau) + } + } + + return pbsReq, nil +} + +func (req PBSRequest) Elapsed() int { + return int(time.Since(req.Start) / 1000000) +} + +func (p PBSRequest) String() string { + b, _ := json.MarshalIndent(p, "", " ") + return string(b) +} + +// parses the "Regs.ext.gdpr" from the request, if it exists. Otherwise returns an empty string. +func (req *PBSRequest) ParseGDPR() string { + if req == nil || req.Regs == nil || len(req.Regs.Ext) == 0 { + return "" + } + val, err := jsonparser.GetInt(req.Regs.Ext, "gdpr") + if err != nil { + return "" + } + gdpr := strconv.Itoa(int(val)) + + return gdpr +} + +// parses the "User.ext.consent" from the request, if it exists. Otherwise returns an empty string. +func (req *PBSRequest) ParseConsent() string { + if req == nil || req.User == nil { + return "" + } + return parseString(req.User.Ext, "consent") +} + +func parseString(data []byte, key string) string { + if len(data) == 0 { + return "" + } + val, err := jsonparser.GetString(data, key) + if err != nil { + return "" + } + return val +} diff --git a/pbs/pbsrequest_test.go b/pbs/pbsrequest_test.go new file mode 100644 index 00000000000..52cd6153323 --- /dev/null +++ b/pbs/pbsrequest_test.go @@ -0,0 +1,735 @@ +package pbs + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/magiconair/properties/assert" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/config" +) + +const mimeVideoMp4 = "video/mp4" +const mimeVideoFlv = "video/x-flv" + +func TestParseMediaTypes(t *testing.T) { + types1 := []string{"Banner"} + t1 := ParseMediaTypes(types1) + assert.Equal(t, len(t1), 1) + assert.Equal(t, t1[0], MEDIA_TYPE_BANNER) + + types2 := []string{"Banner", "Video"} + t2 := ParseMediaTypes(types2) + assert.Equal(t, len(t2), 2) + assert.Equal(t, t2[0], MEDIA_TYPE_BANNER) + assert.Equal(t, t2[1], MEDIA_TYPE_VIDEO) + + types3 := []string{"Banner", "Vo"} + t3 := ParseMediaTypes(types3) + assert.Equal(t, len(t3), 1) + assert.Equal(t, t3[0], MEDIA_TYPE_BANNER) +} + +func TestParseSimpleRequest(t *testing.T) { + body := []byte(`{ + "tid": "abcd", + "ad_units": [ + { + "code": "first", + "sizes": [{"w": 300, "h": 250}], + "bids": [ + { + "bidder": "ix" + }, + { + "bidder": "appnexus" + } + ] + }, + { + "code": "second", + "sizes": [{"w": 728, "h": 90}], + "media_types" :["banner", "video"], + "video" : { + "mimes" : ["video/mp4", "video/x-flv"] + }, + "bids": [ + { + "bidder": "ix" + }, + { + "bidder": "appnexus" + } + ] + } + + ] + } + `) + r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) + r.Header.Add("Referer", "http://nytimes.com/cool.html") + d, _ := dummycache.New() + hcc := config.HostCookie{} + + pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, d, &hcc) + if err != nil { + t.Fatalf("Parse simple request failed: %v", err) + } + if pbs_req.Tid != "abcd" { + t.Errorf("Parse TID failed") + } + if len(pbs_req.AdUnits) != 2 { + t.Errorf("Parse ad units failed") + } + + // see if our internal representation is intact + if len(pbs_req.Bidders) != 2 { + t.Fatalf("Should have two bidders not %d", len(pbs_req.Bidders)) + } + if pbs_req.Bidders[0].BidderCode != "ix" { + t.Errorf("First bidder not index") + } + if len(pbs_req.Bidders[0].AdUnits) != 2 { + t.Errorf("Index bidder should have 2 ad unit") + } + if pbs_req.Bidders[1].BidderCode != "appnexus" { + t.Errorf("Second bidder not appnexus") + } + if len(pbs_req.Bidders[1].AdUnits) != 2 { + t.Errorf("AppNexus bidder should have 2 ad unit") + } + if pbs_req.Bidders[1].AdUnits[0].BidID == "" { + t.Errorf("ID should have been generated for empty BidID") + } + if pbs_req.AdUnits[1].MediaTypes[0] != "banner" { + t.Errorf("Instead of banner MediaType received %s", pbs_req.AdUnits[1].MediaTypes[0]) + } + if pbs_req.AdUnits[1].MediaTypes[1] != "video" { + t.Errorf("Instead of video MediaType received %s", pbs_req.AdUnits[1].MediaTypes[0]) + } + if pbs_req.AdUnits[1].Video.Mimes[0] != mimeVideoMp4 { + t.Errorf("Instead of video/mp4 mimes received %s", pbs_req.AdUnits[1].Video.Mimes) + } + if pbs_req.AdUnits[1].Video.Mimes[1] != mimeVideoFlv { + t.Errorf("Instead of video/flv mimes received %s", pbs_req.AdUnits[1].Video.Mimes) + } + +} + +func TestHeaderParsing(t *testing.T) { + body := []byte(`{ + "tid": "abcd", + "ad_units": [ + { + "code": "first", + "sizes": [{"w": 300, "h": 250}], + "bidders": [ + { + "bidder": "ix", + "params": { + "id": "417", + "siteID": "test-site" + } + } + ] + } + ] + } + `) + r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) + r.Header.Add("Referer", "http://nytimes.com/cool.html") + r.Header.Add("User-Agent", "Mozilla/") + d, _ := dummycache.New() + hcc := config.HostCookie{} + + d.Config().Set("dummy", dummyConfig) + + pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, d, &hcc) + if err != nil { + t.Fatalf("Parse simple request failed") + } + if pbs_req.Url != "http://nytimes.com/cool.html" { + t.Errorf("Failed to pull URL from referrer") + } + if pbs_req.Domain != "nytimes.com" { + t.Errorf("Failed to parse TLD from referrer: %s not nytimes.com", pbs_req.Domain) + } + if pbs_req.Device.UA != "Mozilla/" { + t.Errorf("Failed to pull User-Agent from referrer") + } +} + +var dummyConfig = ` +[ + { + "bidder": "ix", + "bid_id": "22222222", + "params": { + "id": "4", + "siteID": "186774", + "timeout": "10000" + } + + }, + { + "bidder": "audienceNetwork", + "bid_id": "22222225", + "params": { + } + }, + { + "bidder": "pubmatic", + "bid_id": "22222223", + "params": { + "publisherId": "156009", + "adSlot": "39620189@728x90" + } + }, + { + "bidder": "appnexus", + "bid_id": "22222224", + "params": { + "placementId": "1" + } + } + ] + ` + +func TestParseConfig(t *testing.T) { + body := []byte(`{ + "tid": "abcd", + "ad_units": [ + { + "code": "first", + "sizes": [{"w": 300, "h": 250}], + "bids": [ + { + "bidder": "ix" + }, + { + "bidder": "appnexus" + } + ] + }, + { + "code": "second", + "sizes": [{"w": 728, "h": 90}], + "config_id": "abcd" + } + ] + } + `) + r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) + r.Header.Add("Referer", "http://nytimes.com/cool.html") + d, _ := dummycache.New() + hcc := config.HostCookie{} + + d.Config().Set("dummy", dummyConfig) + + pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, d, &hcc) + if err != nil { + t.Fatalf("Parse simple request failed: %v", err) + } + if pbs_req.Tid != "abcd" { + t.Errorf("Parse TID failed") + } + if len(pbs_req.AdUnits) != 2 { + t.Errorf("Parse ad units failed") + } + + // see if our internal representation is intact + if len(pbs_req.Bidders) != 4 { + t.Fatalf("Should have 4 bidders not %d", len(pbs_req.Bidders)) + } + if pbs_req.Bidders[0].BidderCode != "ix" { + t.Errorf("First bidder not index") + } + if len(pbs_req.Bidders[0].AdUnits) != 2 { + t.Errorf("Index bidder should have 1 ad unit") + } + if pbs_req.Bidders[1].BidderCode != "appnexus" { + t.Errorf("Second bidder not appnexus") + } + if len(pbs_req.Bidders[1].AdUnits) != 2 { + t.Errorf("AppNexus bidder should have 2 ad unit") + } +} + +func TestParseMobileRequestFirstVersion(t *testing.T) { + body := []byte(`{ + "max_key_length":20, + "user":{ + "gender":0, + "buyeruid":"test_buyeruid" + }, + "prebid_version":"0.21.0-pre", + "sort_bids":1, + "ad_units":[ + { + "sizes":[ + { + "w":300, + "h":250 + } + ], + "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", + "code":"5d748364ee9c46a2b112892fc3551b6f" + } + ], + "cache_markup":1, + "app":{ + "bundle":"AppNexus.PrebidMobileDemo", + "ver":"0.0.1" + }, + "sdk":{ + "version":"0.0.1", + "platform":"iOS", + "source":"prebid-mobile" + }, + "device":{ + "ifa":"test_device_ifa", + "osv":"9.3.5", + "os":"iOS", + "make":"Apple", + "model":"iPhone6,1" + }, + "tid":"abcd", + "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" + } + `) + r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) + d, _ := dummycache.New() + hcc := config.HostCookie{} + + pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, d, &hcc) + if err != nil { + t.Fatalf("Parse simple request failed: %v", err) + } + if pbs_req.Tid != "abcd" { + t.Errorf("Parse TID failed") + } + if len(pbs_req.AdUnits) != 1 { + t.Errorf("Parse ad units failed") + } + // We are expecting all user fields to be nil. We don't parse user on v0.0.1 of prebid mobile + if pbs_req.User.BuyerUID != "" { + t.Errorf("Parse user buyeruid failed %s", pbs_req.User.BuyerUID) + } + if pbs_req.User.Gender != "" { + t.Errorf("Parse user gender failed %s", pbs_req.User.Gender) + } + if pbs_req.User.Yob != 0 { + t.Errorf("Parse user year of birth failed %d", pbs_req.User.Yob) + } + if pbs_req.User.ID != "" { + t.Errorf("Parse user id failed %s", pbs_req.User.ID) + } + + if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { + t.Errorf("Parse app bundle failed") + } + if pbs_req.App.Ver != "0.0.1" { + t.Errorf("Parse app version failed") + } + + if pbs_req.Device.IFA != "test_device_ifa" { + t.Errorf("Parse device ifa failed") + } + if pbs_req.Device.OSV != "9.3.5" { + t.Errorf("Parse device osv failed") + } + if pbs_req.Device.OS != "iOS" { + t.Errorf("Parse device os failed") + } + if pbs_req.Device.Make != "Apple" { + t.Errorf("Parse device make failed") + } + if pbs_req.Device.Model != "iPhone6,1" { + t.Errorf("Parse device model failed") + } +} + +func TestParseMobileRequest(t *testing.T) { + body := []byte(`{ + "max_key_length":20, + "user":{ + "gender":"F", + "buyeruid":"test_buyeruid", + "yob":2000, + "id":"testid" + }, + "prebid_version":"0.21.0-pre", + "sort_bids":1, + "ad_units":[ + { + "sizes":[ + { + "w":300, + "h":250 + } + ], + "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", + "code":"5d748364ee9c46a2b112892fc3551b6f" + } + ], + "cache_markup":1, + "app":{ + "bundle":"AppNexus.PrebidMobileDemo", + "ver":"0.0.2" + }, + "sdk":{ + "version":"0.0.2", + "platform":"iOS", + "source":"prebid-mobile" + }, + "device":{ + "ifa":"test_device_ifa", + "osv":"9.3.5", + "os":"iOS", + "make":"Apple", + "model":"iPhone6,1" + }, + "tid":"abcd", + "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" + } + `) + r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) + d, _ := dummycache.New() + hcc := config.HostCookie{} + + pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, d, &hcc) + if err != nil { + t.Fatalf("Parse simple request failed: %v", err) + } + if pbs_req.Tid != "abcd" { + t.Errorf("Parse TID failed") + } + if len(pbs_req.AdUnits) != 1 { + t.Errorf("Parse ad units failed") + } + + if pbs_req.User.BuyerUID != "test_buyeruid" { + t.Errorf("Parse user buyeruid failed") + } + if pbs_req.User.Gender != "F" { + t.Errorf("Parse user gender failed") + } + if pbs_req.User.Yob != 2000 { + t.Errorf("Parse user year of birth failed") + } + if pbs_req.User.ID != "testid" { + t.Errorf("Parse user id failed") + } + if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { + t.Errorf("Parse app bundle failed") + } + if pbs_req.App.Ver != "0.0.2" { + t.Errorf("Parse app version failed") + } + + if pbs_req.Device.IFA != "test_device_ifa" { + t.Errorf("Parse device ifa failed") + } + if pbs_req.Device.OSV != "9.3.5" { + t.Errorf("Parse device osv failed") + } + if pbs_req.Device.OS != "iOS" { + t.Errorf("Parse device os failed") + } + if pbs_req.Device.Make != "Apple" { + t.Errorf("Parse device make failed") + } + if pbs_req.Device.Model != "iPhone6,1" { + t.Errorf("Parse device model failed") + } + if pbs_req.SDK.Version != "0.0.2" { + t.Errorf("Parse sdk version failed") + } + if pbs_req.SDK.Source != "prebid-mobile" { + t.Errorf("Parse sdk source failed") + } + if pbs_req.SDK.Platform != "iOS" { + t.Errorf("Parse sdk platform failed") + } + if pbs_req.Device.IP == "" { + t.Errorf("Parse device ip failed %s", pbs_req.Device.IP) + } +} + +func TestParseMalformedMobileRequest(t *testing.T) { + body := []byte(`{ + "max_key_length":20, + "user":{ + "gender":0, + "buyeruid":"test_buyeruid" + }, + "prebid_version":"0.21.0-pre", + "sort_bids":1, + "ad_units":[ + { + "sizes":[ + { + "w":300, + "h":250 + } + ], + "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", + "code":"5d748364ee9c46a2b112892fc3551b6f" + } + ], + "cache_markup":1, + "app":{ + "bundle":"AppNexus.PrebidMobileDemo", + "ver":"0.0.1" + }, + "device":{ + "ifa":"test_device_ifa", + "osv":"9.3.5", + "os":"iOS", + "make":"Apple", + "model":"iPhone6,1" + }, + "tid":"abcd", + "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" + } + `) + r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) + d, _ := dummycache.New() + hcc := config.HostCookie{} + + pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, d, &hcc) + if err != nil { + t.Fatalf("Parse simple request failed: %v", err) + } + if pbs_req.Tid != "abcd" { + t.Errorf("Parse TID failed") + } + if len(pbs_req.AdUnits) != 1 { + t.Errorf("Parse ad units failed") + } + // We are expecting all user fields to be nil. Since no SDK version is passed in + if pbs_req.User.BuyerUID != "" { + t.Errorf("Parse user buyeruid failed %s", pbs_req.User.BuyerUID) + } + if pbs_req.User.Gender != "" { + t.Errorf("Parse user gender failed %s", pbs_req.User.Gender) + } + if pbs_req.User.Yob != 0 { + t.Errorf("Parse user year of birth failed %d", pbs_req.User.Yob) + } + if pbs_req.User.ID != "" { + t.Errorf("Parse user id failed %s", pbs_req.User.ID) + } + + if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { + t.Errorf("Parse app bundle failed") + } + if pbs_req.App.Ver != "0.0.1" { + t.Errorf("Parse app version failed") + } + + if pbs_req.Device.IFA != "test_device_ifa" { + t.Errorf("Parse device ifa failed") + } + if pbs_req.Device.OSV != "9.3.5" { + t.Errorf("Parse device osv failed") + } + if pbs_req.Device.OS != "iOS" { + t.Errorf("Parse device os failed") + } + if pbs_req.Device.Make != "Apple" { + t.Errorf("Parse device make failed") + } + if pbs_req.Device.Model != "iPhone6,1" { + t.Errorf("Parse device model failed") + } +} + +func TestParseRequestWithInstl(t *testing.T) { + body := []byte(`{ + "max_key_length":20, + "user":{ + "gender":"F", + "buyeruid":"test_buyeruid", + "yob":2000, + "id":"testid" + }, + "prebid_version":"0.21.0-pre", + "sort_bids":1, + "ad_units":[ + { + "sizes":[ + { + "w":300, + "h":250 + } + ], + "bids": [ + { + "bidder": "ix" + }, + { + "bidder": "appnexus" + } + ], + "code":"5d748364ee9c46a2b112892fc3551b6f", + "instl": 1 + } + ], + "cache_markup":1, + "app":{ + "bundle":"AppNexus.PrebidMobileDemo", + "ver":"0.0.2" + }, + "sdk":{ + "version":"0.0.2", + "platform":"iOS", + "source":"prebid-mobile" + }, + "device":{ + "ifa":"test_device_ifa", + "osv":"9.3.5", + "os":"iOS", + "make":"Apple", + "model":"iPhone6,1" + }, + "tid":"abcd", + "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" + } + `) + r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) + d, _ := dummycache.New() + hcc := config.HostCookie{} + + pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, d, &hcc) + if err != nil { + t.Fatalf("Parse simple request failed: %v", err) + } + if len(pbs_req.Bidders) != 2 { + t.Errorf("Should have 2 bidders. ") + } + if pbs_req.Bidders[0].AdUnits[0].Instl != 1 { + t.Errorf("Parse instl failed.") + } + if pbs_req.Bidders[1].AdUnits[0].Instl != 1 { + t.Errorf("Parse instl failed.") + } + +} + +func TestTimeouts(t *testing.T) { + doTimeoutTest(t, 10, 15, 10, 0) + doTimeoutTest(t, 10, 0, 10, 0) + doTimeoutTest(t, 5, 5, 10, 0) + doTimeoutTest(t, 15, 15, 0, 0) + doTimeoutTest(t, 15, 0, 20, 15) +} + +func doTimeoutTest(t *testing.T, expected int, requested int, max uint64, def uint64) { + t.Helper() + cfg := &config.AuctionTimeouts{ + Default: def, + Max: max, + } + body := fmt.Sprintf(`{ + "tid": "abcd", + "timeout_millis": %d, + "app":{ + "bundle":"AppNexus.PrebidMobileDemo", + "ver":"0.0.2" + }, + "ad_units": [ + { + "code": "first", + "sizes": [{"w": 300, "h": 250}], + "bids": [ + { + "bidder": "ix" + } + ] + } + ] +}`, requested) + r := httptest.NewRequest("POST", "/auction", strings.NewReader(body)) + d, _ := dummycache.New() + parsed, err := ParsePBSRequest(r, cfg, d, &config.HostCookie{}) + if err != nil { + t.Fatalf("Unexpected err: %v", err) + } + if parsed.TimeoutMillis != int64(expected) { + t.Errorf("Expected %dms timeout, got %dms", expected, parsed.TimeoutMillis) + } +} + +func TestParsePBSRequestUsesHostCookie(t *testing.T) { + body := []byte(`{ + "tid": "abcd", + "ad_units": [ + { + "code": "first", + "sizes": [{"w": 300, "h": 250}], + "bidders": [ + { + "bidder": "bidder1", + "params": { + "id": "417", + "siteID": "test-site" + } + } + ] + } + ] + } + `) + r, err := http.NewRequest("POST", "/auction", bytes.NewBuffer(body)) + r.Header.Add("Referer", "http://nytimes.com/cool.html") + if err != nil { + t.Fatalf("new request failed") + } + r.AddCookie(&http.Cookie{Name: "key", Value: "testcookie"}) + d, _ := dummycache.New() + hcc := config.HostCookie{ + CookieName: "key", + Family: "family", + OptOutCookie: config.Cookie{ + Name: "trp_optout", + Value: "true", + }, + } + + pbs_req, err2 := ParsePBSRequest(r, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, d, &hcc) + if err2 != nil { + t.Fatalf("Parse simple request failed %v", err2) + } + if uid, _, _ := pbs_req.Cookie.GetUID("family"); uid != "testcookie" { + t.Errorf("Failed to leverage host cookie space for user identifier") + } +} diff --git a/pbs/pbsresponse.go b/pbs/pbsresponse.go new file mode 100644 index 00000000000..b8cf2c19ff7 --- /dev/null +++ b/pbs/pbsresponse.go @@ -0,0 +1,84 @@ +package pbs + +// PBSBid is a bid from the auction. These are produced by Adapters, and target a particular Ad Unit. +// +// This JSON format is a contract with both Prebid.js and Prebid-mobile. +// All changes *must* be backwards compatible, since clients cannot be forced to update their code. +type PBSBid struct { + // BidID identifies the Bid Request within the Ad Unit which this Bid targets. It should match one of + // the values inside PBSRequest.AdUnits[i].Bids[j].BidID. + BidID string `json:"bid_id"` + // AdUnitCode identifies the AdUnit which this Bid targets. + // It should match one of PBSRequest.AdUnits[i].Code, where "i" matches the AdUnit used in + // as BidID. + AdUnitCode string `json:"code"` + // Creative_id uniquely identifies the creative being served. It is not used by prebid-server, but + // it helps publishers and bidders identify and communicate about malicious or inappropriate ads. + // This project simply passes it along with the bid. + Creative_id string `json:"creative_id,omitempty"` + // CreativeMediaType shows whether the creative is a video or banner. + CreativeMediaType string `json:"media_type,omitempty"` + // BidderCode is the PBSBidder.BidderCode of the PBSBidder who made this bid. + BidderCode string `json:"bidder"` + // BidHash is the hash of the bidder's unique bid identifier for blockchain. It should not be sent to browser. + BidHash string `json:"-"` + // Price is the cpm, in US Dollars, which the bidder is willing to pay if this bid is chosen. + // TODO: Add support for other currencies someday. + Price float64 `json:"price"` + // NURL is a URL which returns ad markup, and should be called if the bid wins. + // If NURL and Adm are both defined, then Adm takes precedence. + NURL string `json:"nurl,omitempty"` + // Adm is the ad markup which should be used to deliver the ad, if this bid is chosen. + // If NURL and Adm are both defined, then Adm takes precedence. + Adm string `json:"adm,omitempty"` + // Width is the intended width which Adm should be shown, in pixels. + Width int64 `json:"width,omitempty"` + // Height is the intended width which Adm should be shown, in pixels. + Height int64 `json:"height,omitempty"` + // DealId is not used by prebid-server, but may be used by buyers and sellers who make special + // deals with each other. We simply pass this information along with the bid. + DealId string `json:"deal_id,omitempty"` + // CacheId is an ID in prebid-cache which can be used to fetch this ad's content. + // This supports prebid-mobile, which requires that the content be available from a URL. + CacheID string `json:"cache_id,omitempty"` + // Complete cache url returned from the prebid-cache. + // more flexible than a design that assumes the UUID is always appended to the end of the URL. + CacheURL string `json:"cache_url,omitempty"` + // ResponseTime is the number of milliseconds it took for the adapter to return a bid. + ResponseTime int `json:"response_time_ms,omitempty"` + AdServerTargeting map[string]string `json:"ad_server_targeting,omitempty"` +} + +// PBSBidSlice attaches the methods of sort.Interface to []PBSBid, ordering them by price. +// If two prices are equal, then the response time will be used as a tiebreaker. +// For more information, see https://golang.org/pkg/sort/#Interface +type PBSBidSlice []*PBSBid + +func (bids PBSBidSlice) Len() int { + return len(bids) +} + +func (bids PBSBidSlice) Less(i, j int) bool { + bidiResponseTimeInTerras := (float64(bids[i].ResponseTime) / 1000000000.0) + bidjResponseTimeInTerras := (float64(bids[j].ResponseTime) / 1000000000.0) + return bids[i].Price-bidiResponseTimeInTerras > bids[j].Price-bidjResponseTimeInTerras +} + +func (bids PBSBidSlice) Swap(i, j int) { + bids[i], bids[j] = bids[j], bids[i] +} + +type BidderDebug struct { + RequestURI string `json:"request_uri,omitempty"` + RequestBody string `json:"request_body,omitempty"` + ResponseBody string `json:"response_body,omitempty"` + StatusCode int `json:"status_code,omitempty"` +} + +type PBSResponse struct { + TID string `json:"tid,omitempty"` + Status string `json:"status,omitempty"` + BidderStatus []*PBSBidder `json:"bidder_status,omitempty"` + Bids PBSBidSlice `json:"bids,omitempty"` + BUrl string `json:"burl,omitempty"` +} diff --git a/pbs/pbsresponse_test.go b/pbs/pbsresponse_test.go new file mode 100644 index 00000000000..0e51120cdf4 --- /dev/null +++ b/pbs/pbsresponse_test.go @@ -0,0 +1,88 @@ +package pbs + +import ( + "sort" + "testing" +) + +func TestSortBids(t *testing.T) { + bid1 := PBSBid{ + BidID: "testBidId", + AdUnitCode: "testAdUnitCode", + BidderCode: "testBidderCode", + Price: 0.0, + } + bid2 := PBSBid{ + BidID: "testBidId", + AdUnitCode: "testAdUnitCode", + BidderCode: "testBidderCode", + Price: 4.0, + } + bid3 := PBSBid{ + BidID: "testBidId", + AdUnitCode: "testAdUnitCode", + BidderCode: "testBidderCode", + Price: 2.0, + } + bid4 := PBSBid{ + BidID: "testBidId", + AdUnitCode: "testAdUnitCode", + BidderCode: "testBidderCode", + Price: 0.50, + } + + bids := make(PBSBidSlice, 0) + bids = append(bids, &bid1, &bid2, &bid3, &bid4) + + sort.Sort(bids) + if bids[0].Price != 4.0 { + t.Error("Expected 4.00 to be highest price") + } + if bids[1].Price != 2.0 { + t.Error("Expected 2.00 to be second highest price") + } + if bids[2].Price != 0.5 { + t.Error("Expected 0.50 to be third highest price") + } + if bids[3].Price != 0.0 { + t.Error("Expected 0.00 to be lowest price") + } +} + +func TestSortBidsWithResponseTimes(t *testing.T) { + bid1 := PBSBid{ + BidID: "testBidId", + AdUnitCode: "testAdUnitCode", + BidderCode: "testBidderCode", + Price: 1.0, + ResponseTime: 70, + } + bid2 := PBSBid{ + BidID: "testBidId", + AdUnitCode: "testAdUnitCode", + BidderCode: "testBidderCode", + Price: 1.0, + ResponseTime: 20, + } + bid3 := PBSBid{ + BidID: "testBidId", + AdUnitCode: "testAdUnitCode", + BidderCode: "testBidderCode", + Price: 1.0, + ResponseTime: 99, + } + + bids := make(PBSBidSlice, 0) + bids = append(bids, &bid1, &bid2, &bid3) + + sort.Sort(bids) + if bids[0] != &bid2 { + t.Error("Expected bid 2 to win") + } + if bids[1] != &bid1 { + t.Error("Expected bid 1 to be second") + } + if bids[2] != &bid3 { + t.Error("Expected bid 3 to be last") + } +} diff --git a/pbs/usersync.go b/pbs/usersync.go index d5043b8c13f..85b55f42aeb 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -7,10 +7,13 @@ import ( "net/http" "net/url" "strings" + "time" "github.com/golang/glog" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/server/ssl" "github.com/prebid/prebid-server/usersync" ) @@ -18,10 +21,27 @@ import ( // Recaptcha code from https://github.com/haisum/recaptcha/blob/master/recaptcha.go const RECAPTCHA_URL = "https://www.google.com/recaptcha/api/siteverify" +const ( + USERSYNC_OPT_OUT = "usersync.opt_outs" + USERSYNC_BAD_REQUEST = "usersync.bad_requests" + USERSYNC_SUCCESS = "usersync.%s.sets" +) + +// uidWithExpiry bundles the UID with an Expiration date. +// After the expiration, the UID is no longer valid. +type uidWithExpiry struct { + // UID is the ID given to a user by a particular bidder + UID string `json:"uid"` + // Expires is the time at which this UID should no longer apply. + Expires time.Time `json:"expires"` +} + type UserSyncDeps struct { ExternalUrl string RecaptchaSecret string HostCookieConfig *config.HostCookie + MetricsEngine metrics.MetricsEngine + PBSAnalytics analytics.PBSAnalyticsModule } // Struct for parsing json in google's response @@ -59,7 +79,7 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr rr := r.FormValue("g-recaptcha-response") if rr == "" { - http.Redirect(w, r, fmt.Sprintf("%s/static/optout.html", deps.ExternalUrl), http.StatusMovedPermanently) + http.Redirect(w, r, fmt.Sprintf("%s/static/optout.html", deps.ExternalUrl), 301) return } @@ -78,8 +98,8 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr pc.SetCookieOnResponse(w, false, deps.HostCookieConfig, deps.HostCookieConfig.TTLDuration()) if optout == "" { - http.Redirect(w, r, deps.HostCookieConfig.OptInURL, http.StatusMovedPermanently) + http.Redirect(w, r, deps.HostCookieConfig.OptInURL, 301) } else { - http.Redirect(w, r, deps.HostCookieConfig.OptOutURL, http.StatusMovedPermanently) + http.Redirect(w, r, deps.HostCookieConfig.OptOutURL, 301) } } diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index a24a139ea1d..730d54b0acb 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -120,7 +120,7 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s responseBody, err := ioutil.ReadAll(anResp.Body) if anResp.StatusCode != 200 { - logError(&errs, "Prebid Cache call to %s returned %d: %s", c.putUrl, anResp.StatusCode, responseBody) + logError(&errs, "Prebid Cache call to %s returned %d: %s", putURL, anResp.StatusCode, responseBody) return uuidsToReturn, errs } diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index ec390364849..60237bbbb27 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -280,18 +280,10 @@ func assertStringEqual(t *testing.T, expected, actual string) { } } -type handlerResponseObject struct { - UUID string `json:"uuid"` -} - -type handlerResponse struct { - Responses []handlerResponseObject `json:"responses"` -} - func newHandler(numResponses int) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := handlerResponse{ - Responses: make([]handlerResponseObject, numResponses), + resp := response{ + Responses: make([]responseObject, numResponses), } for i := 0; i < numResponses; i++ { resp.Responses[i].UUID = strconv.Itoa(i) diff --git a/prebid_cache_client/prebid_cache.go b/prebid_cache_client/prebid_cache.go new file mode 100644 index 00000000000..cde7ec8d951 --- /dev/null +++ b/prebid_cache_client/prebid_cache.go @@ -0,0 +1,122 @@ +package prebid_cache_client + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + + "golang.org/x/net/context/ctxhttp" +) + +// This file is deprecated, and is only used to cache things for the legacy (/auction) endpoint. +// For /openrtb2/auction cache, see client.go in this package. + +type CacheObject struct { + Value interface{} + UUID string + IsVideo bool +} + +type BidCache struct { + Adm string `json:"adm,omitempty"` + NURL string `json:"nurl,omitempty"` + Width int64 `json:"width,omitempty"` + Height int64 `json:"height,omitempty"` +} + +// internal protocol objects +type putObject struct { + Type string `json:"type"` + Value interface{} `json:"value"` +} + +type putRequest struct { + Puts []putObject `json:"puts"` +} + +type responseObject struct { + UUID string `json:"uuid"` +} +type response struct { + Responses []responseObject `json:"responses"` +} + +var ( + client *http.Client + baseURL string + putURL string +) + +// InitPrebidCache setup the global prebid cache +func InitPrebidCache(baseurl string) { + baseURL = baseurl + putURL = fmt.Sprintf("%s/cache", baseURL) + + ts := &http.Transport{ + MaxIdleConns: 10, + IdleConnTimeout: 65, + } + + client = &http.Client{ + Transport: ts, + } +} + +// Put will send the array of objs and update each with a UUID +func Put(ctx context.Context, objs []*CacheObject) error { + // Fixes #197 + if len(objs) == 0 { + return nil + } + pr := putRequest{Puts: make([]putObject, len(objs))} + for i, obj := range objs { + if obj.IsVideo { + pr.Puts[i].Type = "xml" + } else { + pr.Puts[i].Type = "json" + } + pr.Puts[i].Value = obj.Value + } + // Don't want to escape the HTML for adm and nurl + buf := new(bytes.Buffer) + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + err := enc.Encode(pr) + if err != nil { + return err + } + + httpReq, err := http.NewRequest("POST", putURL, buf) + if err != nil { + return err + } + httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") + httpReq.Header.Add("Accept", "application/json") + + anResp, err := ctxhttp.Do(ctx, client, httpReq) + if err != nil { + return err + } + defer anResp.Body.Close() + + if anResp.StatusCode != 200 { + return fmt.Errorf("HTTP status code %d", anResp.StatusCode) + } + + var resp response + if err := json.NewDecoder(anResp.Body).Decode(&resp); err != nil { + return err + } + + if len(resp.Responses) != len(objs) { + return fmt.Errorf("Put response length didn't match") + } + + for i, r := range resp.Responses { + objs[i].UUID = r.UUID + } + + return nil +} diff --git a/prebid_cache_client/prebid_cache_test.go b/prebid_cache_client/prebid_cache_test.go new file mode 100644 index 00000000000..65688789fd0 --- /dev/null +++ b/prebid_cache_client/prebid_cache_test.go @@ -0,0 +1,150 @@ +package prebid_cache_client + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "fmt" +) + +var delay time.Duration +var ( + MaxValueLength = 1024 * 10 + MaxNumValues = 10 +) + +type putAnyObject struct { + Type string `json:"type"` + Value json.RawMessage `json:"value"` +} + +type putAnyRequest struct { + Puts []putAnyObject `json:"puts"` +} + +func DummyPrebidCacheServer(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Failed to read the request body.", http.StatusBadRequest) + return + } + defer r.Body.Close() + var put putAnyRequest + + err = json.Unmarshal(body, &put) + if err != nil { + http.Error(w, "Request body "+string(body)+" is not valid JSON.", http.StatusBadRequest) + return + } + + if len(put.Puts) > MaxNumValues { + http.Error(w, fmt.Sprintf("More keys than allowed: %d", MaxNumValues), http.StatusBadRequest) + return + } + + resp := response{ + Responses: make([]responseObject, len(put.Puts)), + } + for i, p := range put.Puts { + resp.Responses[i].UUID = fmt.Sprintf("UUID-%d", i+1) // deterministic for testing + if len(p.Value) > MaxValueLength { + http.Error(w, fmt.Sprintf("Value is larger than allowed size: %d", MaxValueLength), http.StatusBadRequest) + return + } + if len(p.Value) == 0 { + http.Error(w, "Missing value.", http.StatusBadRequest) + return + } + if p.Type != "xml" && p.Type != "json" { + http.Error(w, fmt.Sprintf("Type must be one of [\"json\", \"xml\"]. Found %v", p.Type), http.StatusBadRequest) + return + } + } + + bytes, err := json.Marshal(&resp) + if err != nil { + http.Error(w, "Failed to serialize UUIDs into JSON.", http.StatusInternalServerError) + return + } + if delay > 0 { + <-time.After(delay) + } + w.Header().Set("Content-Type", "application/json") + w.Write(bytes) +} + +func TestPrebidClient(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer)) + defer server.Close() + + cobj := make([]*CacheObject, 3) + + // example bids + cobj[0] = &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\"}", + NURL: "https://www.facebook.com/audiencenetwork/nurl/?partner=442648859414574&app=1995257847363113&placement=1997038003851764&auction=d3013e9e-ca55-4a86-9baa-d44e31355e1d&impression=bannerad1&request=7187783259538616534&bid=3832427901228167009&ortb_loss_code=0", + Width: 300, + Height: 250, + }, + } + cobj[1] = &CacheObject{ + IsVideo: false, + Value: &BidCache{ + Adm: "", + Width: 300, + Height: 250, + }, + } + cobj[2] = &CacheObject{ + IsVideo: true, + Value: "", + } + InitPrebidCache(server.URL) + + ctx := context.TODO() + err := Put(ctx, cobj) + if err != nil { + t.Fatalf("pbc put failed: %v", err) + } + + if cobj[0].UUID != "UUID-1" { + t.Errorf("First object UUID was '%s', should have been 'UUID-1'", cobj[0].UUID) + } + if cobj[1].UUID != "UUID-2" { + t.Errorf("Second object UUID was '%s', should have been 'UUID-2'", cobj[1].UUID) + } + if cobj[2].UUID != "UUID-3" { + t.Errorf("Third object UUID was '%s', should have been 'UUID-3'", cobj[2].UUID) + } + + delay = 5 * time.Millisecond + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + err = Put(ctx, cobj) + if err == nil { + t.Fatalf("pbc put succeeded but should have timed out") + } +} + +// Prevents #197 +func TestEmptyBids(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Errorf("The server should not be called.") + }) + server := httptest.NewServer(handler) + defer server.Close() + + InitPrebidCache(server.URL) + + if err := Put(context.Background(), []*CacheObject{}); err != nil { + t.Errorf("Error on Put: %v", err) + } +} diff --git a/privacy/ccpa/parsedpolicy_test.go b/privacy/ccpa/parsedpolicy_test.go index 4bbb4cbfd0f..33563b50567 100644 --- a/privacy/ccpa/parsedpolicy_test.go +++ b/privacy/ccpa/parsedpolicy_test.go @@ -3,7 +3,9 @@ package ccpa import ( "testing" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) func TestValidateConsent(t *testing.T) { @@ -378,3 +380,12 @@ func TestShouldEnforce(t *testing.T) { assert.Equal(t, test.expected, result, test.description) } } + +type mockPolicWriter struct { + mock.Mock +} + +func (m *mockPolicWriter) Write(req *openrtb2.BidRequest) error { + args := m.Called(req) + return args.Error(0) +} diff --git a/router/router.go b/router/router.go index 81623f13838..90074753a5b 100644 --- a/router/router.go +++ b/router/router.go @@ -3,6 +3,7 @@ package router import ( "context" "crypto/tls" + "database/sql" "encoding/json" "fmt" "io/ioutil" @@ -11,17 +12,34 @@ import ( "strings" "time" + "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/endpoints/events" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/prebid-server/version" + + "github.com/prebid/prebid-server/metrics" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adform" + "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/pubmatic" + "github.com/prebid/prebid-server/adapters/pulsepoint" + "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sovrn" analyticsConf "github.com/prebid/prebid-server/analytics/config" + "github.com/prebid/prebid-server/cache" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/cache/filecache" + "github.com/prebid/prebid-server/cache/postgrescache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/endpoints" - "github.com/prebid/prebid-server/endpoints/events" infoEndpoints "github.com/prebid/prebid-server/endpoints/info" "github.com/prebid/prebid-server/endpoints/openrtb2" - "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" @@ -31,8 +49,6 @@ import ( storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/sliceutil" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -40,6 +56,9 @@ import ( "github.com/rs/cors" ) +var dataCache cache.Cache +var exchanges map[string]adapters.Adapter + // NewJsonDirectoryServer is used to serve .json files from a directory as a single blob. For example, // given a directory containing the files "a.json" and "b.json", this returns a Handle which serves JSON like: // @@ -104,6 +123,51 @@ func (m NoCache) ServeHTTP(w http.ResponseWriter, r *http.Request) { m.Handler.ServeHTTP(w, r) } +func loadDataCache(cfg *config.Configuration, db *sql.DB) (err error) { + switch cfg.DataCache.Type { + case "dummy": + dataCache, err = dummycache.New() + if err != nil { + glog.Fatalf("Dummy cache not configured: %s", err.Error()) + } + + case "postgres": + if db == nil { + return fmt.Errorf("Nil db cannot connect to postgres. Did you forget to set the config.stored_requests.postgres values?") + } + dataCache = postgrescache.New(db, postgrescache.CacheConfig{ + Size: cfg.DataCache.CacheSize, + TTL: cfg.DataCache.TTLSeconds, + }) + return nil + case "filecache": + dataCache, err = filecache.New(cfg.DataCache.Filename) + if err != nil { + return fmt.Errorf("FileCache Error: %s", err.Error()) + } + + default: + return fmt.Errorf("Unknown datacache.type: %s", cfg.DataCache.Type) + } + return nil +} + +func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter { + // These keys _must_ coincide with the bidder code in Prebid.js, if the adapter exists in both projects + return map[string]adapters.Adapter{ + "appnexus": appnexus.NewAppNexusLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), + "districtm": appnexus.NewAppNexusLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), + "ix": ix.NewIxLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint), + "pubmatic": pubmatic.NewPubmaticLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), + "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), + "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), + } +} + type Router struct { *httprouter.Router MetricsEngine *metricsConf.DetailedMetricsEngine @@ -147,6 +211,10 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R }, } + // Hack because of how legacy handles districtm + legacyBidderList := openrtb_ext.CoreBidderNames() + legacyBidderList = append(legacyBidderList, openrtb_ext.BidderName("districtm")) + p, _ := filepath.Abs(infoDirectory) bidderInfos, err := config.LoadBidderInfoFromDisk(p, cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) if err != nil { @@ -176,10 +244,13 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } // Metrics engine - r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.CoreBidderNames(), syncerKeys) - shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) + r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList, syncerKeys) + db, shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) // todo(zachbadgett): better shutdown r.Shutdown = shutdown + if err := loadDataCache(cfg, db); err != nil { + return nil, fmt.Errorf("Prebid Server could not load data cache: %v", err) + } pbsAnalytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics) @@ -199,6 +270,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R gvlVendorIDs := bidderInfos.ToGVLVendorIDMap() gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, gvlVendorIDs, generalHttpClient) + exchanges = newExchangeMap(cfg) cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, bidderInfos, r.MetricsEngine) @@ -229,6 +301,10 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, metrics.ReqTypeVideo) } + if cfg.EnableLegacyAuction { + r.POST("/auction", endpoints.Auction(cfg, syncersByBidder, gdprPerms, r.MetricsEngine, dataCache, exchanges)) + } + r.POST("/openrtb2/auction", openrtbEndpoint) r.POST("/openrtb2/video", videoEndpoint) r.GET("/openrtb2/amp", ampEndpoint) @@ -255,6 +331,8 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R HostCookieConfig: &(cfg.HostCookie), ExternalUrl: cfg.ExternalURL, RecaptchaSecret: cfg.RecaptchaSecret, + MetricsEngine: r.MetricsEngine, + PBSAnalytics: pbsAnalytics, } r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg.HostCookie, syncersByBidder, gdprPerms, pbsAnalytics, r.MetricsEngine)) diff --git a/router/router_test.go b/router/router_test.go index b4ceaff16a9..24a7709c365 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "os" "testing" "github.com/prebid/prebid-server/config" @@ -61,6 +62,18 @@ func TestNewJsonDirectoryServer(t *testing.T) { ensureHasKey(t, data, "aliastest") } +func TestExchangeMap(t *testing.T) { + exchanges := newExchangeMap(&config.Configuration{}) + bidderMap := openrtb_ext.BuildBidderMap() + for bidderName := range exchanges { + // OpenRTB doesn't support hardcoded aliases... so this test skips districtm, + // which was the only alias in the legacy adapter map. + if _, ok := bidderMap[bidderName]; bidderName != "districtm" && !ok { + t.Errorf("Bidder %s exists in exchange, but is not a part of the BidderMap.", bidderName) + } + } +} + func TestApplyBidderInfoConfigOverrides(t *testing.T) { var testCases = []struct { description string @@ -285,6 +298,38 @@ func TestNoCache(t *testing.T) { } } +func TestLoadDataCache(t *testing.T) { + // Test dummy + if err := loadDataCache(&config.Configuration{ + DataCache: config.DataCache{ + Type: "dummy", + }, + }, nil); err != nil { + t.Errorf("data cache: dummy: %s", err) + } + // Test postgres error + if err := loadDataCache(&config.Configuration{ + DataCache: config.DataCache{ + Type: "postgres", + }, + }, nil); err == nil { + t.Errorf("data cache: postgres: db nil should return error") + } + // Test file + d, _ := ioutil.TempDir("", "pbs-filecache") + defer os.RemoveAll(d) + f, _ := ioutil.TempFile(d, "file") + defer f.Close() + if err := loadDataCache(&config.Configuration{ + DataCache: config.DataCache{ + Type: "filecache", + Filename: f.Name(), + }, + }, nil); err != nil { + t.Errorf("data cache: filecache: %s", err) + } +} + var testDefReqConfig = config.DefReqConfig{ Type: "file", FileSystem: config.DefReqFiles{ diff --git a/server/prometheus.go b/server/prometheus.go index 6f0a0b2df45..4b9f7037d0a 100644 --- a/server/prometheus.go +++ b/server/prometheus.go @@ -9,10 +9,13 @@ import ( "github.com/prebid/prebid-server/config" metricsconfig "github.com/prebid/prebid-server/metrics/config" + prometheusMetrics "github.com/prebid/prebid-server/metrics/prometheus" ) func newPrometheusServer(cfg *config.Configuration, metrics *metricsconfig.DetailedMetricsEngine) *http.Server { - proMetrics := metrics.PrometheusMetrics + var proMetrics *prometheusMetrics.Metrics + + proMetrics = metrics.PrometheusMetrics if proMetrics == nil { glog.Fatal("Prometheus metrics configured, but a Prometheus metrics engine was not found. Cannot set up a Prometheus listener.") diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index 7d23f942d56..a145a3b43a2 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -32,7 +32,7 @@ func TestAccountFetcher(t *testing.T) { assertErrorCount(t, 0, errs) assert.JSONEq(t, `{"disabled":false, "id":"valid"}`, string(account)) - _, errs = fetcher.FetchAccount(context.Background(), "nonexistent") + account, errs = fetcher.FetchAccount(context.Background(), "nonexistent") assertErrorCount(t, 1, errs) assert.Error(t, errs[0]) assert.Equal(t, stored_requests.NotFoundError{"nonexistent", "Account"}, errs[0]) diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index 75a92e3f331..bc12caecb98 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -74,6 +74,7 @@ func NewFetcher(client *http.Client, endpoint string) *HttpFetcher { type HttpFetcher struct { client *http.Client Endpoint string + hasQuery bool Categories map[string]map[string]stored_requests.Category } diff --git a/stored_requests/backends/http_fetcher/fetcher_test.go b/stored_requests/backends/http_fetcher/fetcher_test.go index 10d3984a818..30933181e1d 100644 --- a/stored_requests/backends/http_fetcher/fetcher_test.go +++ b/stored_requests/backends/http_fetcher/fetcher_test.go @@ -1,8 +1,10 @@ package http_fetcher import ( + "bytes" "context" "encoding/json" + "io" "net/http" "net/http/httptest" "strings" @@ -166,6 +168,42 @@ func TestErrResponse(t *testing.T) { assert.Len(t, errs, 1) } +func assertSameContents(t *testing.T, expected map[string]json.RawMessage, actual map[string]json.RawMessage) { + if len(expected) != len(actual) { + t.Errorf("Wrong counts. Expected %d, actual %d", len(expected), len(actual)) + return + } + for expectedKey, expectedVal := range expected { + if actualVal, ok := actual[expectedKey]; ok { + if !bytes.Equal(expectedVal, actualVal) { + t.Errorf("actual[%s] value %s does not match expected: %s", expectedKey, string(actualVal), string(actualVal)) + } + } else { + t.Errorf("actual map missing expected key %s", expectedKey) + } + } +} + +func assertSameErrMsgs(t *testing.T, expected []string, actual []error) { + if len(expected) != len(actual) { + t.Errorf("Wrong error counts. Expected %d, actual %d", len(expected), len(actual)) + return + } + for i, expectedErr := range expected { + if actual[i].Error() != expectedErr { + t.Errorf("Wrong error[%d]. Expected %s, got %s", i, expectedErr, actual[i].Error()) + } + } +} + +type closeWrapper struct { + io.Reader +} + +func (w closeWrapper) Close() error { + return nil +} + func newFetcherBrokenBackend() (fetcher *HttpFetcher, closer func()) { handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) diff --git a/stored_requests/caches/nil_cache/nil_cache.go b/stored_requests/caches/nil_cache/nil_cache.go index 88bbd404674..d043ae55c96 100644 --- a/stored_requests/caches/nil_cache/nil_cache.go +++ b/stored_requests/caches/nil_cache/nil_cache.go @@ -13,7 +13,9 @@ func (c *NilCache) Get(ctx context.Context, ids []string) map[string]json.RawMes } func (c *NilCache) Save(ctx context.Context, data map[string]json.RawMessage) { + return } func (c *NilCache) Invalidate(ctx context.Context, ids []string) { + return } diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index 89022582ace..f682ff932f4 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -93,12 +93,12 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.Metr return } -// NewStoredRequests returns: +// NewStoredRequests returns five things: // -// 1. A function which should be called on shutdown for graceful cleanups. -// 2. A Fetcher which can be used to get Stored Requests for /openrtb2/auction -// 3. A Fetcher which can be used to get Stored Requests for /openrtb2/amp -// 4. A Fetcher which can be used to get Account data +// 1. A DB connection, if one was created. This may be nil. +// 2. A function which should be called on shutdown for graceful cleanups. +// 3. A Fetcher which can be used to get Stored Requests for /openrtb2/auction +// 4. A Fetcher which can be used to get Stored Requests for /openrtb2/amp // 5. A Fetcher which can be used to get Category Mapping data // 6. A Fetcher which can be used to get Stored Requests for /openrtb2/video // @@ -107,7 +107,7 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.Metr // // As a side-effect, it will add some endpoints to the router if the config calls for it. // In the future we should look for ways to simplify this so that it's not doing two things. -func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router) (shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { +func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router) (db *sql.DB, shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { // TODO: Switch this to be set in config defaults //if cfg.CategoryMapping.CacheEvents.Enabled && cfg.CategoryMapping.CacheEvents.Endpoint == "" { // cfg.CategoryMapping.CacheEvents.Endpoint = "/storedrequest/categorymapping" @@ -121,6 +121,8 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsE fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, &dbc) fetcher5, shutdown5 := CreateStoredRequests(&cfg.Accounts, metricsEngine, client, router, &dbc) + db = dbc.db + fetcher = fetcher1.(stored_requests.Fetcher) ampFetcher = fetcher2.(stored_requests.Fetcher) categoriesFetcher = fetcher3.(stored_requests.CategoryFetcher) diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go index 7cb8f4b9b6d..5b89943572f 100644 --- a/stored_requests/events/events.go +++ b/stored_requests/events/events.go @@ -77,7 +77,7 @@ func (e *EventListener) Listen(cache stored_requests.Cache, events EventProducer e.onInvalidate() } case <-e.stop: - return + break } } } diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index 4e87db5dd0a..4792db1969f 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -33,7 +33,7 @@ func TestEmptyOptOutCookie(t *testing.T) { func TestEmptyCookie(t *testing.T) { cookie := &Cookie{ - uids: make(map[string]uidWithExpiry), + uids: make(map[string]uidWithExpiry, 0), optOut: false, birthday: timestamp(), } From 84b169d57ac450af557110d070a5f4f9dc30df48 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 18 Oct 2021 13:36:03 -0400 Subject: [PATCH 115/140] Engagebdr: Remove GVL ID (#2046) --- static/bidder-info/engagebdr.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/static/bidder-info/engagebdr.yaml b/static/bidder-info/engagebdr.yaml index f79bca69953..0f9cc12fd89 100644 --- a/static/bidder-info/engagebdr.yaml +++ b/static/bidder-info/engagebdr.yaml @@ -1,6 +1,5 @@ maintainer: email: "tech@engagebdr.com" -gvlVendorID: 62 capabilities: app: mediaTypes: From 05b36576fd7e8eb14c3324031554de3b23ebe9c9 Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Mon, 18 Oct 2021 20:36:24 +0300 Subject: [PATCH 116/140] Adf adapter: new bidder params added, multiformat bids supported (#2043) --- adapters/adf/adf.go | 22 ++--- .../adf/adftest/exemplary/dynamic-tag.json | 99 +++++++++++++++++++ .../adf/adftest/exemplary/multi-format.json | 52 +++++++++- .../adf/adftest/exemplary/multi-native.json | 28 +++++- .../adf/adftest/exemplary/single-banner.json | 14 ++- .../adf/adftest/exemplary/single-native.json | 14 ++- .../adf/adftest/exemplary/single-video.json | 14 ++- .../supplemental/invalid-imp-mediatype.json | 36 ++++++- adapters/adf/params_test.go | 8 ++ openrtb_ext/imp_adf.go | 4 +- static/bidder-params/adf.json | 16 ++- 11 files changed, 277 insertions(+), 30 deletions(-) create mode 100644 adapters/adf/adftest/exemplary/dynamic-tag.json diff --git a/adapters/adf/adf.go b/adapters/adf/adf.go index f73e23aa07d..8014bd5bb56 100644 --- a/adapters/adf/adf.go +++ b/adapters/adf/adf.go @@ -95,7 +95,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R var errors []error for _, seatBid := range response.SeatBid { for i, bid := range seatBid.Bid { - bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp) + bidType, err := getMediaTypeForBid(bid) if err != nil { errors = append(errors, err) continue @@ -110,20 +110,16 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R 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 - } +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + if bid.Ext != nil { + var bidExt openrtb_ext.ExtBid + err := json.Unmarshal(bid.Ext, &bidExt) + if err == nil && bidExt.Prebid != nil { + return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)) } } - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("Failed to find supported impression \"%s\" mediatype", impID), + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID), } } diff --git a/adapters/adf/adftest/exemplary/dynamic-tag.json b/adapters/adf/adftest/exemplary/dynamic-tag.json new file mode 100644 index 00000000000..be366a24807 --- /dev/null +++ b/adapters/adf/adftest/exemplary/dynamic-tag.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "inv": 1234, + "mname": "placement" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "test-imp-id2", + "ext": { + "bidder": { + "mid": 1111, + "inv": 4321, + "mname": "placement1" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 300 + }] + } + }], + "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": { + "inv": 1234, + "mname": "placement" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }, { + "id": "test-imp-id2", + "tagid": "1111", + "ext": { + "bidder": { + "mid": 1111, + "inv": 4321, + "mname": "placement1" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 300 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 204 + } + }], + "expectedMakeRequestsErrors": [], + "expectedBidResponses": [] +} diff --git a/adapters/adf/adftest/exemplary/multi-format.json b/adapters/adf/adftest/exemplary/multi-format.json index 6b917658cdc..b0e9d98f440 100644 --- a/adapters/adf/adftest/exemplary/multi-format.json +++ b/adapters/adf/adftest/exemplary/multi-format.json @@ -13,6 +13,12 @@ "video/mp4" ], "placement": 1 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] } }, { "id": "test-imp-id-2", @@ -21,6 +27,12 @@ "mid": "828783" } }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, "banner": { "format": [{ "w": 300, @@ -47,6 +59,12 @@ ], "placement": 1 }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, "tagid": "828782" }, { "id": "test-imp-id-2", @@ -55,6 +73,12 @@ "mid": "828783" } }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, "banner": { "format": [{ "w": 300, @@ -76,7 +100,12 @@ "price": 10, "adm": "{video xml}", "adomain": [], - "crid": "test-creative-id-1" + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "video" + } + } }] }, { "bid": [{ @@ -85,7 +114,12 @@ "price": 2, "adm": "{banner html}", "adomain": [ "ad-domain" ], - "crid": "test-creative-id-2" + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "banner" + } + } }] }], "cur": "EUR" @@ -100,7 +134,12 @@ "impid": "test-imp-id-1", "price": 10, "adm": "{video xml}", - "crid": "test-creative-id-1" + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "video" + } + } }, "type": "video" }, { @@ -110,7 +149,12 @@ "price": 2, "adm": "{banner html}", "adomain": [ "ad-domain" ], - "crid": "test-creative-id-2" + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "banner" + } + } }, "type": "banner" }] diff --git a/adapters/adf/adftest/exemplary/multi-native.json b/adapters/adf/adftest/exemplary/multi-native.json index 40bd7e15773..6c9279b7db6 100644 --- a/adapters/adf/adftest/exemplary/multi-native.json +++ b/adapters/adf/adftest/exemplary/multi-native.json @@ -68,14 +68,24 @@ "price": 10, "adm": "{json response string 1}", "adomain": [], - "crid": "test-creative-id-1" + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "native" + } + } }, { "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" + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "native" + } + } }] }], "cur": "EUR" @@ -90,7 +100,12 @@ "impid": "test-imp-id-1", "price": 10, "adm": "{json response string 1}", - "crid": "test-creative-id-1" + "crid": "test-creative-id-1", + "ext": { + "prebid": { + "type": "native" + } + } }, "type": "native" }, { @@ -100,7 +115,12 @@ "price": 2, "adm": "{json response string 2}", "adomain": [ "ad-domain" ], - "crid": "test-creative-id-2" + "crid": "test-creative-id-2", + "ext": { + "prebid": { + "type": "native" + } + } }, "type": "native" }] diff --git a/adapters/adf/adftest/exemplary/single-banner.json b/adapters/adf/adftest/exemplary/single-banner.json index 5fdfe8af7c8..2fc869024c1 100644 --- a/adapters/adf/adftest/exemplary/single-banner.json +++ b/adapters/adf/adftest/exemplary/single-banner.json @@ -69,7 +69,12 @@ "price": 10, "adm": "{banner html}", "adomain": [ "test.com" ], - "crid": "test-creative-id" + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "banner" + } + } }] }], "cur": "USD" @@ -86,7 +91,12 @@ "price": 10, "adm": "{banner html}", "crid": "test-creative-id", - "adomain": [ "test.com" ] + "adomain": [ "test.com" ], + "ext": { + "prebid": { + "type": "banner" + } + } }, "type": "banner" } diff --git a/adapters/adf/adftest/exemplary/single-native.json b/adapters/adf/adftest/exemplary/single-native.json index 909f8cec9a7..347c4aebcc6 100644 --- a/adapters/adf/adftest/exemplary/single-native.json +++ b/adapters/adf/adftest/exemplary/single-native.json @@ -65,7 +65,12 @@ "price": 10, "adm": "{json response string}", "adomain": [], - "crid": "test-creative-id" + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "native" + } + } }] }], "cur": "USD" @@ -81,7 +86,12 @@ "impid": "test-imp-id", "price": 10, "adm": "{json response string}", - "crid": "test-creative-id" + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "native" + } + } }, "type": "native" } diff --git a/adapters/adf/adftest/exemplary/single-video.json b/adapters/adf/adftest/exemplary/single-video.json index ebe4c6f1b38..2e8c0f953e2 100644 --- a/adapters/adf/adftest/exemplary/single-video.json +++ b/adapters/adf/adftest/exemplary/single-video.json @@ -68,7 +68,12 @@ "impid": "test-imp-id", "price": 10, "adm": "{vast xml}", - "crid": "test-creative-id" + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "video" + } + } }] }], "cur": "USD" @@ -84,7 +89,12 @@ "impid": "test-imp-id", "price": 10, "adm": "{vast xml}", - "crid": "test-creative-id" + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "video" + } + } }, "type": "video" } diff --git a/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json b/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json index 2f01e7eaae9..8a87492a2dc 100644 --- a/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json +++ b/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json @@ -9,6 +9,14 @@ "mid": 12345 } } + },{ + "id": "test-imp-id-2", + "audio": {}, + "ext": { + "bidder": { + "mid": 12345 + } + } }] }, "httpCalls": [ @@ -28,6 +36,17 @@ "mimes": null }, "tagid": "12345" + }, { + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id-2", + "audio": { + "mimes": null + }, + "tagid": "12345" }] } }, @@ -42,6 +61,17 @@ "price": 10, "adm": "{vast xml}", "crid": "test-creative-id" + }, { + "id": "test-bid-id", + "impid": "test-imp-id-2", + "price": 10, + "adm": "{vast xml}", + "crid": "test-creative-id", + "ext": { + "prebid": { + "type": "not-a-banner" + } + } }] }], "cur": "USD" @@ -52,7 +82,11 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "Failed to find supported impression \"test-imp-id\" mediatype", + "value": "Failed to parse impression \"test-imp-id\" mediatype", + "comparison": "literal" + }, + { + "value": "invalid BidType: not-a-banner", "comparison": "literal" } ] diff --git a/adapters/adf/params_test.go b/adapters/adf/params_test.go index 779d3fb6f2d..d075f914082 100644 --- a/adapters/adf/params_test.go +++ b/adapters/adf/params_test.go @@ -42,6 +42,10 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"mid":123}`, `{"mid":"123"}`, + `{"inv":321,"mname":"pcl1"}`, + `{"inv":321,"mname":"12345"}`, + `{"mid":123,"inv":321,"mname":"pcl1"}`, + `{"mid":"123","inv":321,"mname":"pcl1"}`, } var invalidParams = []string{ @@ -54,4 +58,8 @@ var invalidParams = []string{ `{}`, `{"notmid":"123"}`, `{"mid":"placementID"}`, + `{"inv":321,"mname":12345}`, + `{"inv":321}`, + `{"inv":"321"}`, + `{"mname":"12345"}`, } diff --git a/openrtb_ext/imp_adf.go b/openrtb_ext/imp_adf.go index 8ef02a460b2..6a8f9afaa33 100644 --- a/openrtb_ext/imp_adf.go +++ b/openrtb_ext/imp_adf.go @@ -5,5 +5,7 @@ import ( ) type ExtImpAdf struct { - MasterTagID json.Number `json:"mid"` + MasterTagID json.Number `json:"mid,omitempty"` + InventorySourceID int `json:"inv,omitempty"` + PlacementName string `json:"mname,omitempty"` } diff --git a/static/bidder-params/adf.json b/static/bidder-params/adf.json index a16df36d681..4a1a11827ef 100644 --- a/static/bidder-params/adf.json +++ b/static/bidder-params/adf.json @@ -8,7 +8,21 @@ "type": ["integer", "string"], "pattern": "^\\d+$", "description": "An ID which identifies the placement selling the impression" + }, + "inv": { + "type": ["integer"], + "description": "An ID which identifies the Adform inventory source id" + }, + "mname": { + "type": ["string"], + "description": "A Name which identifies the placement selling the impression" } }, - "required": ["mid"] + "anyOf":[ + { + "required": ["mid"] + }, { + "required": ["inv", "mname"] + } + ] } From a6a036d4560a64b454c1c4ea7e52f4a51f7e8d90 Mon Sep 17 00:00:00 2001 From: Bugxyb Date: Tue, 19 Oct 2021 01:36:53 +0800 Subject: [PATCH 117/140] Algorix: add placementId Support (#2035) --- .../sample-banner-with-palcementid.json | 89 +++++++++++++++++++ .../algorixtest/exemplary/sample-native.json | 8 +- .../algorixtest/exemplary/sample-nobid.json | 8 +- .../algorixtest/exemplary/sample-video.json | 8 +- adapters/algorix/params_test.go | 5 +- openrtb_ext/imp_algorix.go | 6 +- static/bidder-params/algorix.json | 8 ++ 7 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 adapters/algorix/algorixtest/exemplary/sample-banner-with-palcementid.json diff --git a/adapters/algorix/algorixtest/exemplary/sample-banner-with-palcementid.json b/adapters/algorix/algorixtest/exemplary/sample-banner-with-palcementid.json new file mode 100644 index 00000000000..4d1853f0236 --- /dev/null +++ b/adapters/algorix/algorixtest/exemplary/sample-banner-with-palcementid.json @@ -0,0 +1,89 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [{"w": 320, "h": 50}] + }, + "tagid": "test123", + "ext": { + "bidder": { + "sid": "testSid", + "token": "testToken", + "placementId": "testPlacementId", + "appId": "testAppId" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://test.com?sid=testSid&token=testToken", + "body": { + "id": "test-request-id", + "imp": [ + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 320, "h": 50}], + "w": 320, + "h": 50 + }, + "tagid": "test123", + "ext": { + "bidder": { + "sid": "testSid", + "token": "testToken", + "placementId": "testPlacementId", + "appId": "testAppId" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "ttx", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-ads", + "crid": "crid_testid" + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-ads", + "crid": "crid_testid" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/algorix/algorixtest/exemplary/sample-native.json b/adapters/algorix/algorixtest/exemplary/sample-native.json index 283e6a2bfb4..3b24bd3a4d3 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-native.json +++ b/adapters/algorix/algorixtest/exemplary/sample-native.json @@ -11,7 +11,9 @@ "ext": { "bidder": { "sid": "testSid", - "token": "testToken" + "token": "testToken", + "placementId": "testPlacementId", + "appId": "testAppId" } } } @@ -33,7 +35,9 @@ "ext": { "bidder": { "sid": "testSid", - "token": "testToken" + "token": "testToken", + "placementId": "testPlacementId", + "appId": "testAppId" } } } diff --git a/adapters/algorix/algorixtest/exemplary/sample-nobid.json b/adapters/algorix/algorixtest/exemplary/sample-nobid.json index 4ea2e61e5d2..c378ff0df05 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-nobid.json +++ b/adapters/algorix/algorixtest/exemplary/sample-nobid.json @@ -10,7 +10,9 @@ "ext": { "bidder": { "sid": "testSid", - "token": "testToken" + "token": "testToken", + "placementId": "testPlacementId", + "appId": "testAppId" } } } @@ -34,7 +36,9 @@ "ext": { "bidder": { "sid": "testSid", - "token": "testToken" + "token": "testToken", + "placementId": "testPlacementId", + "appId": "testAppId" } } } diff --git a/adapters/algorix/algorixtest/exemplary/sample-video.json b/adapters/algorix/algorixtest/exemplary/sample-video.json index 5507e6a9d38..df3f6dcdc41 100644 --- a/adapters/algorix/algorixtest/exemplary/sample-video.json +++ b/adapters/algorix/algorixtest/exemplary/sample-video.json @@ -14,7 +14,9 @@ "ext": { "bidder": { "sid": "testSid", - "token": "testToken" + "token": "testToken", + "placementId": "testPlacementId", + "appId": "testAppId" } } } @@ -40,7 +42,9 @@ "ext": { "bidder": { "sid": "testSid", - "token": "testToken" + "token": "testToken", + "placementId": "testPlacementId", + "appId": "testAppId" } } } diff --git a/adapters/algorix/params_test.go b/adapters/algorix/params_test.go index 18a2779fe4a..3830b372188 100644 --- a/adapters/algorix/params_test.go +++ b/adapters/algorix/params_test.go @@ -34,7 +34,7 @@ func TestInvalidParams(t *testing.T) { } var validParams = []string{ - `{"sid": "11233", "token": "sin"}`, + `{"sid": "11233", "token": "sin", "placementId": "123", "appId": "abc"}`, `{"sid": "11244", "token": "iad"}`, } @@ -44,4 +44,7 @@ var invalidParams = []string{ `{"sid": 123, "token": "sin"}`, `{"sid": "", "token": "iad"}`, `{"sid": "11233", "token": ""}`, + `{"sid": "11233", "token": "sin", "placementId": 123, "appId": "abc"}`, + `{"sid": "11233", "token": "sin", "placementId": "123", "appId": 456}`, + `{"sid": "11233", "token": "sin", "placementId": 123, "appId": 456}`, } diff --git a/openrtb_ext/imp_algorix.go b/openrtb_ext/imp_algorix.go index e63a45ee11e..3661018d657 100644 --- a/openrtb_ext/imp_algorix.go +++ b/openrtb_ext/imp_algorix.go @@ -2,6 +2,8 @@ package openrtb_ext // ExtImpAlgoriX defines the contract for bidrequest.imp[i].ext.algorix type ExtImpAlgorix struct { - Sid string `json:"sid"` - Token string `json:"token"` + Sid string `json:"sid"` + Token string `json:"token"` + PlacementId string `json:"placementId"` + AppId string `json:"appId"` } diff --git a/static/bidder-params/algorix.json b/static/bidder-params/algorix.json index 732625f4549..675d9793bb5 100644 --- a/static/bidder-params/algorix.json +++ b/static/bidder-params/algorix.json @@ -13,6 +13,14 @@ "type": "string", "description": "Your Token", "minLength": 1 + }, + "placementId": { + "type": "string", + "description": "An ID which identifies this placement of the impression" + }, + "appId": { + "type": "string", + "description": "An ID which identifies this app of the impression" } }, "required": ["sid", "token"] From 47e480b79666721f9295d3c4088ec6177ae5e1c2 Mon Sep 17 00:00:00 2001 From: Brian Sardo <1168933+bsardo@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:02:47 -0400 Subject: [PATCH 118/140] GDPR: deprecate per-purpose enabled config & add enforce purpose (#2036) --- config/config.go | 98 ++++++++++++-- config/config_test.go | 295 ++++++++++++++++++++++++++++++++++++++++++ gdpr/impl.go | 4 +- gdpr/impl_test.go | 52 ++++---- 4 files changed, 409 insertions(+), 40 deletions(-) diff --git a/config/config.go b/config/config.go index 597ac1b41a7..49a9d2dbcdf 100644 --- a/config/config.go +++ b/config/config.go @@ -229,6 +229,31 @@ func (cfg *GDPR) validate(v *viper.Viper, 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)")) } + return cfg.validatePurposes(errs) +} + +func (cfg *GDPR) validatePurposes(errs []error) []error { + purposeConfigs := []TCF2Purpose{ + cfg.TCF2.Purpose1, + cfg.TCF2.Purpose2, + cfg.TCF2.Purpose3, + cfg.TCF2.Purpose4, + cfg.TCF2.Purpose5, + cfg.TCF2.Purpose6, + cfg.TCF2.Purpose7, + cfg.TCF2.Purpose8, + cfg.TCF2.Purpose9, + cfg.TCF2.Purpose10, + } + + for i := 0; i < len(purposeConfigs); i++ { + enforcePurposeValue := purposeConfigs[i].EnforcePurpose + enforcePurposeField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", (i + 1)) + + if enforcePurposeValue != TCF2NoEnforcement && enforcePurposeValue != TCF2FullEnforcement { + errs = append(errs, fmt.Errorf("%s must be \"no\" or \"full\". Got %s", enforcePurposeField, enforcePurposeValue)) + } + } return errs } @@ -245,6 +270,11 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond } +const ( + TCF2FullEnforcement = "full" + TCF2NoEnforcement = "no" +) + // TCF2 defines the TCF2 specific configurations for GDPR type TCF2 struct { Enabled bool `mapstructure:"enabled"` @@ -264,8 +294,9 @@ type TCF2 struct { // Making a purpose struct so purpose specific details can be added later. type TCF2Purpose struct { - Enabled bool `mapstructure:"enabled"` - EnforceVendors bool `mapstructure:"enforce_vendors"` + Enabled bool `mapstructure:"enabled"` // Deprecated: Use enforce_purpose instead + EnforcePurpose string `mapstructure:"enforce_purpose"` + 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{} @@ -900,16 +931,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) v.SetDefault("gdpr.tcf2.enabled", true) - v.SetDefault("gdpr.tcf2.purpose1.enabled", true) - v.SetDefault("gdpr.tcf2.purpose2.enabled", true) - v.SetDefault("gdpr.tcf2.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) @@ -989,7 +1010,31 @@ func SetupViper(v *viper.Viper, filename string) { // Migrate config settings to maintain compatibility with old configs migrateConfig(v) migrateConfigPurposeOneTreatment(v) + migrateConfigTCF2PurposeEnabledFlags(v) + // These defaults must be set after the migrate functions because those functions look for the presence of these + // config fields and there isn't a way to detect presence of a config field using the viper package if a default + // is set. Viper IsSet and Get functions consider default values. + 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_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose2.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose3.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose4.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose5.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose6.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose7.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose8.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose9.enforce_purpose", TCF2FullEnforcement) + v.SetDefault("gdpr.tcf2.purpose10.enforce_purpose", TCF2FullEnforcement) v.SetDefault("gdpr.tcf2.purpose_one_treatment.enabled", true) v.SetDefault("gdpr.tcf2.purpose_one_treatment.access_allowed", true) } @@ -1019,6 +1064,35 @@ func migrateConfigPurposeOneTreatment(v *viper.Viper) { } } +func migrateConfigTCF2PurposeEnabledFlags(v *viper.Viper) { + for i := 1; i <= 10; i++ { + oldField := fmt.Sprintf("gdpr.tcf2.purpose%d.enabled", i) + newField := fmt.Sprintf("gdpr.tcf2.purpose%d.enforce_purpose", i) + + if v.IsSet(oldField) { + oldConfig := v.GetBool(oldField) + if v.IsSet(newField) { + glog.Warningf("using %s and ignoring deprecated %s", newField, oldField) + } else { + glog.Warningf("%s is deprecated and should be changed to %s", oldField, newField) + if oldConfig { + v.Set(newField, TCF2FullEnforcement) + } else { + v.Set(newField, TCF2NoEnforcement) + } + } + } + + if v.IsSet(newField) { + if v.GetString(newField) == TCF2FullEnforcement { + v.Set(oldField, "true") + } else { + v.Set(oldField, "false") + } + } + } +} + func setBidderDefaults(v *viper.Viper, bidder string) { adapterCfgPrefix := "adapters." + bidder v.SetDefault(adapterCfgPrefix+".endpoint", "") diff --git a/config/config_test.go b/config/config_test.go index 819eb21f819..a2c5c952d84 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -151,60 +151,70 @@ func TestDefaults(t *testing.T) { Enabled: true, Purpose1: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose2: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose3: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose4: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose5: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose6: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose7: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose8: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose9: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, }, Purpose10: TCF2Purpose{ Enabled: true, + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: true, VendorExceptions: []openrtb_ext.BidderName{}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, @@ -237,6 +247,7 @@ gdpr: vendor_exceptions: ["foo1a", "foo1b"] purpose2: enabled: false + enforce_purpose: "no" enforce_vendors: false vendor_exceptions: ["foo2"] purpose3: @@ -467,60 +478,70 @@ func TestFullConfig(t *testing.T) { Enabled: true, Purpose1: TCF2Purpose{ Enabled: true, // true by default + EnforcePurpose: TCF2FullEnforcement, 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, + EnforcePurpose: TCF2NoEnforcement, 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 + EnforcePurpose: TCF2FullEnforcement, 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 + EnforcePurpose: TCF2FullEnforcement, 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 + EnforcePurpose: TCF2FullEnforcement, 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 + EnforcePurpose: TCF2FullEnforcement, 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 + EnforcePurpose: TCF2FullEnforcement, 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 + EnforcePurpose: TCF2FullEnforcement, 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 + EnforcePurpose: TCF2FullEnforcement, 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 + EnforcePurpose: TCF2FullEnforcement, EnforceVendors: false, VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")}, VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}}, @@ -616,6 +637,18 @@ func TestValidateConfig(t *testing.T) { cfg := Configuration{ GDPR: GDPR{ DefaultValue: "1", + TCF2: TCF2{ + Purpose1: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose2: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose3: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose4: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose5: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose6: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose7: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose8: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose9: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + Purpose10: TCF2Purpose{EnforcePurpose: TCF2FullEnforcement}, + }, }, StoredRequests: StoredRequests{ Files: FileFetcherConfig{Enabled: true}, @@ -743,6 +776,244 @@ func TestMigrateConfigPurposeOneTreatment(t *testing.T) { } } +func TestMigrateConfigTCF2PurposeEnabledFlags(t *testing.T) { + trueStr := "true" + falseStr := "false" + + tests := []struct { + description string + config []byte + wantPurpose1EnforcePurpose string + wantPurpose2EnforcePurpose string + wantPurpose3EnforcePurpose string + wantPurpose4EnforcePurpose string + wantPurpose5EnforcePurpose string + wantPurpose6EnforcePurpose string + wantPurpose7EnforcePurpose string + wantPurpose8EnforcePurpose string + wantPurpose9EnforcePurpose string + wantPurpose10EnforcePurpose string + wantPurpose1Enabled string + wantPurpose2Enabled string + wantPurpose3Enabled string + wantPurpose4Enabled string + wantPurpose5Enabled string + wantPurpose6Enabled string + wantPurpose7Enabled string + wantPurpose8Enabled string + wantPurpose9Enabled string + wantPurpose10Enabled string + }{ + { + description: "New config and old config flags not set", + config: []byte{}, + }, + { + description: "New config not set, old config set - use old flags", + config: []byte(` + gdpr: + tcf2: + purpose1: + enabled: false + purpose2: + enabled: true + purpose3: + enabled: false + purpose4: + enabled: true + purpose5: + enabled: false + purpose6: + enabled: true + purpose7: + enabled: false + purpose8: + enabled: true + purpose9: + enabled: false + purpose10: + enabled: true + `), + wantPurpose1EnforcePurpose: TCF2NoEnforcement, + wantPurpose2EnforcePurpose: TCF2FullEnforcement, + wantPurpose3EnforcePurpose: TCF2NoEnforcement, + wantPurpose4EnforcePurpose: TCF2FullEnforcement, + wantPurpose5EnforcePurpose: TCF2NoEnforcement, + wantPurpose6EnforcePurpose: TCF2FullEnforcement, + wantPurpose7EnforcePurpose: TCF2NoEnforcement, + wantPurpose8EnforcePurpose: TCF2FullEnforcement, + wantPurpose9EnforcePurpose: TCF2NoEnforcement, + wantPurpose10EnforcePurpose: TCF2FullEnforcement, + wantPurpose1Enabled: falseStr, + wantPurpose2Enabled: trueStr, + wantPurpose3Enabled: falseStr, + wantPurpose4Enabled: trueStr, + wantPurpose5Enabled: falseStr, + wantPurpose6Enabled: trueStr, + wantPurpose7Enabled: falseStr, + wantPurpose8Enabled: trueStr, + wantPurpose9Enabled: falseStr, + wantPurpose10Enabled: trueStr, + }, + { + description: "New config flags set, old config flags not set - use new flags", + config: []byte(` + gdpr: + tcf2: + purpose1: + enforce_purpose: "full" + purpose2: + enforce_purpose: "no" + purpose3: + enforce_purpose: "full" + purpose4: + enforce_purpose: "no" + purpose5: + enforce_purpose: "full" + purpose6: + enforce_purpose: "no" + purpose7: + enforce_purpose: "full" + purpose8: + enforce_purpose: "no" + purpose9: + enforce_purpose: "full" + purpose10: + enforce_purpose: "no" + `), + wantPurpose1EnforcePurpose: TCF2FullEnforcement, + wantPurpose2EnforcePurpose: TCF2NoEnforcement, + wantPurpose3EnforcePurpose: TCF2FullEnforcement, + wantPurpose4EnforcePurpose: TCF2NoEnforcement, + wantPurpose5EnforcePurpose: TCF2FullEnforcement, + wantPurpose6EnforcePurpose: TCF2NoEnforcement, + wantPurpose7EnforcePurpose: TCF2FullEnforcement, + wantPurpose8EnforcePurpose: TCF2NoEnforcement, + wantPurpose9EnforcePurpose: TCF2FullEnforcement, + wantPurpose10EnforcePurpose: TCF2NoEnforcement, + wantPurpose1Enabled: trueStr, + wantPurpose2Enabled: falseStr, + wantPurpose3Enabled: trueStr, + wantPurpose4Enabled: falseStr, + wantPurpose5Enabled: trueStr, + wantPurpose6Enabled: falseStr, + wantPurpose7Enabled: trueStr, + wantPurpose8Enabled: falseStr, + wantPurpose9Enabled: trueStr, + wantPurpose10Enabled: falseStr, + }, + { + description: "New config flags and old config flags set - use new flags", + config: []byte(` + gdpr: + tcf2: + purpose1: + enabled: false + enforce_purpose: "full" + purpose2: + enabled: false + enforce_purpose: "full" + purpose3: + enabled: false + enforce_purpose: "full" + purpose4: + enabled: false + enforce_purpose: "full" + purpose5: + enabled: false + enforce_purpose: "full" + purpose6: + enabled: false + enforce_purpose: "full" + purpose7: + enabled: false + enforce_purpose: "full" + purpose8: + enabled: false + enforce_purpose: "full" + purpose9: + enabled: false + enforce_purpose: "full" + purpose10: + enabled: false + enforce_purpose: "full" + `), + wantPurpose1EnforcePurpose: TCF2FullEnforcement, + wantPurpose2EnforcePurpose: TCF2FullEnforcement, + wantPurpose3EnforcePurpose: TCF2FullEnforcement, + wantPurpose4EnforcePurpose: TCF2FullEnforcement, + wantPurpose5EnforcePurpose: TCF2FullEnforcement, + wantPurpose6EnforcePurpose: TCF2FullEnforcement, + wantPurpose7EnforcePurpose: TCF2FullEnforcement, + wantPurpose8EnforcePurpose: TCF2FullEnforcement, + wantPurpose9EnforcePurpose: TCF2FullEnforcement, + wantPurpose10EnforcePurpose: TCF2FullEnforcement, + wantPurpose1Enabled: trueStr, + wantPurpose2Enabled: trueStr, + wantPurpose3Enabled: trueStr, + wantPurpose4Enabled: trueStr, + wantPurpose5Enabled: trueStr, + wantPurpose6Enabled: trueStr, + wantPurpose7Enabled: trueStr, + wantPurpose8Enabled: trueStr, + wantPurpose9Enabled: trueStr, + wantPurpose10Enabled: trueStr, + }, + } + + for _, tt := range tests { + v := viper.New() + v.SetConfigType("yaml") + v.ReadConfig(bytes.NewBuffer(tt.config)) + + migrateConfigTCF2PurposeEnabledFlags(v) + + if len(tt.config) > 0 { + assert.Equal(t, tt.wantPurpose1EnforcePurpose, v.GetString("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose2EnforcePurpose, v.GetString("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose3EnforcePurpose, v.GetString("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose4EnforcePurpose, v.GetString("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose5EnforcePurpose, v.GetString("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose6EnforcePurpose, v.GetString("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose7EnforcePurpose, v.GetString("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose8EnforcePurpose, v.GetString("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose9EnforcePurpose, v.GetString("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose10EnforcePurpose, v.GetString("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) + assert.Equal(t, tt.wantPurpose1Enabled, v.GetString("gdpr.tcf2.purpose1.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose2Enabled, v.GetString("gdpr.tcf2.purpose2.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose3Enabled, v.GetString("gdpr.tcf2.purpose3.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose4Enabled, v.GetString("gdpr.tcf2.purpose4.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose5Enabled, v.GetString("gdpr.tcf2.purpose5.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose6Enabled, v.GetString("gdpr.tcf2.purpose6.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose7Enabled, v.GetString("gdpr.tcf2.purpose7.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose8Enabled, v.GetString("gdpr.tcf2.purpose8.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose9Enabled, v.GetString("gdpr.tcf2.purpose9.enabled"), tt.description) + assert.Equal(t, tt.wantPurpose10Enabled, v.GetString("gdpr.tcf2.purpose10.enabled"), tt.description) + } else { + assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enforce_purpose"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose1.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose2.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose3.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose4.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose5.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose6.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose7.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose8.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose9.enabled"), tt.description) + assert.Nil(t, v.Get("gdpr.tcf2.purpose10.enabled"), tt.description) + } + } +} + func TestInvalidAdapterEndpointConfig(t *testing.T) { v := viper.New() SetupViper(v, "") @@ -817,6 +1088,30 @@ func TestMissingGDPRDefaultValue(t *testing.T) { assertOneError(t, cfg.validate(v), "gdpr.default_value is required and must be specified") } +func TestInvalidEnforcePurpose(t *testing.T) { + cfg, v := newDefaultConfig(t) + cfg.GDPR.TCF2.Purpose1.EnforcePurpose = "" + cfg.GDPR.TCF2.Purpose2.EnforcePurpose = TCF2NoEnforcement + cfg.GDPR.TCF2.Purpose3.EnforcePurpose = TCF2NoEnforcement + cfg.GDPR.TCF2.Purpose4.EnforcePurpose = TCF2NoEnforcement + cfg.GDPR.TCF2.Purpose5.EnforcePurpose = "invalid1" + cfg.GDPR.TCF2.Purpose6.EnforcePurpose = "invalid2" + cfg.GDPR.TCF2.Purpose7.EnforcePurpose = TCF2FullEnforcement + cfg.GDPR.TCF2.Purpose8.EnforcePurpose = TCF2FullEnforcement + cfg.GDPR.TCF2.Purpose9.EnforcePurpose = TCF2FullEnforcement + cfg.GDPR.TCF2.Purpose10.EnforcePurpose = "invalid3" + + errs := cfg.validate(v) + + expectedErrs := []error{ + errors.New("gdpr.tcf2.purpose1.enforce_purpose must be \"no\" or \"full\". Got "), + errors.New("gdpr.tcf2.purpose5.enforce_purpose must be \"no\" or \"full\". Got invalid1"), + errors.New("gdpr.tcf2.purpose6.enforce_purpose must be \"no\" or \"full\". Got invalid2"), + errors.New("gdpr.tcf2.purpose10.enforce_purpose must be \"no\" or \"full\". Got invalid3"), + } + assert.ElementsMatch(t, errs, expectedErrs, "gdpr.tcf2.purposeX.enforce_purpose should prevent invalid values but it doesn't") +} + func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { v := viper.New() v.Set("gdpr.default_value", "0") diff --git a/gdpr/impl.go b/gdpr/impl.go index 9fb55462725..07aad36d765 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -96,7 +96,7 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } - if !p.cfg.TCF2.Purpose1.Enabled { + if p.cfg.TCF2.Purpose1.EnforcePurpose == config.TCF2NoEnforcement { return true, nil } consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) @@ -138,7 +138,7 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, } else { passGeo = true } - if p.cfg.TCF2.Purpose2.Enabled { + if p.cfg.TCF2.Purpose2.EnforcePurpose == config.TCF2FullEnforcement { vendorException := p.isVendorException(consentconstants.Purpose(2), bidder) allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), vendorException, weakVendorEnforcement) } else { diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index f9da82a80a5..3064a9fda89 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -65,7 +65,7 @@ func TestAllowedSyncs(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true, }, }, @@ -109,7 +109,7 @@ func TestProhibitedPurposes(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + EnforcePurpose: config.TCF2FullEnforcement, }, }, }, @@ -148,7 +148,7 @@ func TestProhibitedVendors(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Purpose1: config.TCF2Purpose{ - Enabled: true, + EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true, }, }, @@ -296,7 +296,7 @@ func TestAllowActivities(t *testing.T) { TCF2: config.TCF2{ Enabled: true, Purpose2: config.TCF2Purpose{ - Enabled: true, + EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true, }, }, @@ -374,16 +374,16 @@ func allPurposesEnabledPermissions() (perms permissionsImpl) { 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}, + Purpose1: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose2: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose3: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose4: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose5: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose6: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose7: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose8: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose9: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, + Purpose10: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true}, SpecialPurpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, }, }, @@ -707,7 +707,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { testDefs := []struct { description string - purpose2Enabled bool + purpose2EnforcePurpose string purpose2EnforceVendors bool bidder openrtb_ext.BidderName consent string @@ -718,7 +718,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }{ { description: "Bid blocked - p2 enabled, user consents to p2 but not vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderPubmatic, consent: purpose2ConsentWithoutVendorConsent, @@ -728,7 +728,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 but not vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: false, bidder: openrtb_ext.BidderPubmatic, consent: purpose2ConsentWithoutVendorConsent, @@ -738,7 +738,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 disabled, user consents to p2 but not vendor, vendor consents to p2", - purpose2Enabled: false, + purpose2EnforcePurpose: config.TCF2NoEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderPubmatic, consent: purpose2ConsentWithoutVendorConsent, @@ -748,7 +748,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled, user consents to p2 and vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderPubmatic, consent: purpose2AndVendorConsent, @@ -758,7 +758,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid blocked - p2 enabled, user consents to p2 LI but not vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderRubicon, consent: purpose2LIWithoutVendorLI, @@ -768,7 +768,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled, user consents to p2 LI and vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: true, bidder: openrtb_ext.BidderRubicon, consent: purpose2AndVendorLI, @@ -778,7 +778,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) { }, { description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 LI but not vendor, vendor consents to p2", - purpose2Enabled: true, + purpose2EnforcePurpose: config.TCF2FullEnforcement, purpose2EnforceVendors: false, bidder: openrtb_ext.BidderPubmatic, consent: purpose2AndVendorLI, @@ -801,9 +801,9 @@ func TestAllowActivitiesBidRequests(t *testing.T) { 34: parseVendorListDataV2(t, vendorListData), }), } - perms.cfg.TCF2.Purpose2.Enabled = td.purpose2Enabled + perms.cfg.TCF2.Purpose2.EnforcePurpose = td.purpose2EnforcePurpose p2Config := perms.purposeConfigs[consentconstants.Purpose(2)] - p2Config.Enabled = td.purpose2Enabled + p2Config.EnforcePurpose = td.purpose2EnforcePurpose p2Config.EnforceVendors = td.purpose2EnforceVendors perms.purposeConfigs[consentconstants.Purpose(2)] = p2Config @@ -895,7 +895,7 @@ func TestAllowActivitiesVendorException(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, - Purpose2: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p2VendorExceptionMap}, + Purpose2: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, VendorExceptionMap: td.p2VendorExceptionMap}, SpecialPurpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.sp1VendorExceptionMap}, }, }, @@ -962,7 +962,7 @@ func TestBidderSyncAllowedVendorException(t *testing.T) { HostVendorID: 2, TCF2: config.TCF2{ Enabled: true, - Purpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p1VendorExceptionMap}, + Purpose1: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, VendorExceptionMap: td.p1VendorExceptionMap}, }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ From 607fa0f2bbc2a229ebbf8e03751c71840ae85130 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 21 Oct 2021 11:58:11 -0400 Subject: [PATCH 119/140] Remove /auction Endpoint (#2049) --- adapters/adform/adform.go | 173 +--- adapters/adform/adform_test.go | 226 +---- adapters/adhese/adhese.go | 2 +- adapters/adman/adman.go | 4 - adapters/appnexus/appnexus.go | 230 +---- adapters/appnexus/appnexus_test.go | 421 +-------- adapters/connectad/connectad.go | 4 - adapters/consumable/instant.go | 2 +- adapters/consumable/utils.go | 20 - adapters/conversant/cnvr_legacy.go | 291 ------ adapters/conversant/cnvr_legacy_test.go | 853 ------------------ adapters/deepintent/deepintent.go | 4 - adapters/dmx/dmx_test.go | 4 - adapters/infoawarebidder_test.go | 1 - adapters/ix/ix.go | 248 +---- adapters/ix/ix_test.go | 703 +-------------- adapters/legacy.go | 97 -- adapters/openrtb_util.go | 174 ---- adapters/openrtb_util_test.go | 543 ----------- adapters/pubmatic/pubmatic.go | 315 +------ adapters/pubmatic/pubmatic_test.go | 673 +------------- adapters/pulsepoint/pulsepoint.go | 201 +---- adapters/pulsepoint/pulsepoint_test.go | 294 +----- adapters/rubicon/rubicon.go | 335 +------ adapters/rubicon/rubicon_test.go | 658 +------------- adapters/sharethrough/utils_test.go | 4 - adapters/sonobi/sonobi.go | 10 - adapters/sovrn/sovrn.go | 167 +--- adapters/sovrn/sovrn_test.go | 278 +----- analytics/filesystem/file_module.go | 2 - analytics/pubstack/pubstack_module_test.go | 44 - cache/dummycache/dummycache.go | 65 -- cache/dummycache/dummycache_test.go | 31 - cache/filecache/filecache.go | 123 --- cache/filecache/filecache_test.go | 79 -- cache/legacy.go | 33 - cache/postgrescache/postgrescache.go | 139 --- cache/postgrescache/postgrescache_test.go | 94 -- config/config.go | 17 - config/config_test.go | 13 - config/stored_requests.go | 1 - config/structlog.go | 4 +- endpoints/auction.go | 513 ----------- endpoints/auction_test.go | 654 -------------- endpoints/openrtb2/auction.go | 9 - endpoints/openrtb2/auction_test.go | 16 - endpoints/openrtb2/video_auction_test.go | 16 - exchange/auction.go | 7 - exchange/auction_test.go | 6 - exchange/bidder_test.go | 1 - exchange/exchange_test.go | 2 +- exchange/targeting_test.go | 19 +- go.mod | 3 - go.sum | 4 - main.go | 3 - metrics/config/metrics.go | 11 - metrics/config/metrics_test.go | 2 - metrics/go_metrics.go | 10 - metrics/go_metrics_test.go | 4 - metrics/metrics.go | 1 - metrics/metrics_mock.go | 5 - metrics/prometheus/prometheus.go | 4 - metrics/prometheus/prometheus_test.go | 22 - metrics/prometheus/type_conversion.go | 9 - openrtb_ext/bidders.go | 2 - openrtb_ext/imp_beachfront.go | 6 +- openrtb_ext/imp_nanointeractive.go | 8 +- pbs/pbsrequest.go | 403 --------- pbs/pbsrequest_test.go | 735 --------------- pbs/pbsresponse.go | 84 -- pbs/pbsresponse_test.go | 88 -- pbs/usersync.go | 26 +- prebid_cache_client/client.go | 2 +- prebid_cache_client/client_test.go | 12 +- prebid_cache_client/prebid_cache.go | 122 --- prebid_cache_client/prebid_cache_test.go | 150 --- privacy/ccpa/parsedpolicy_test.go | 11 - router/router.go | 94 +- router/router_test.go | 45 - server/prometheus.go | 5 +- .../backends/file_fetcher/fetcher_test.go | 2 +- .../backends/http_fetcher/fetcher.go | 1 - .../backends/http_fetcher/fetcher_test.go | 38 - stored_requests/caches/nil_cache/nil_cache.go | 2 - stored_requests/config/config.go | 14 +- stored_requests/events/events.go | 2 +- usersync/cookie_test.go | 2 +- 87 files changed, 107 insertions(+), 10648 deletions(-) delete mode 100644 adapters/consumable/utils.go delete mode 100644 adapters/conversant/cnvr_legacy.go delete mode 100644 adapters/conversant/cnvr_legacy_test.go delete mode 100644 adapters/legacy.go delete mode 100644 adapters/openrtb_util.go delete mode 100644 adapters/openrtb_util_test.go delete mode 100644 cache/dummycache/dummycache.go delete mode 100644 cache/dummycache/dummycache_test.go delete mode 100644 cache/filecache/filecache.go delete mode 100644 cache/filecache/filecache_test.go delete mode 100644 cache/legacy.go delete mode 100644 cache/postgrescache/postgrescache.go delete mode 100644 cache/postgrescache/postgrescache_test.go delete mode 100644 endpoints/auction.go delete mode 100644 endpoints/auction_test.go delete mode 100644 pbs/pbsrequest.go delete mode 100644 pbs/pbsrequest_test.go delete mode 100644 pbs/pbsresponse.go delete mode 100644 pbs/pbsresponse_test.go delete mode 100644 prebid_cache_client/prebid_cache.go delete mode 100644 prebid_cache_client/prebid_cache_test.go diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index 225c7af35d4..a432bb075b3 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -2,31 +2,27 @@ package adform import ( "bytes" - "context" "encoding/base64" "encoding/json" "errors" "fmt" - "io/ioutil" "net/http" "net/url" "strconv" "strings" - "github.com/buger/jsonparser" - "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" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const version = "0.1.3" type AdformAdapter struct { - http *adapters.HTTPAdapter URL *url.URL version string } @@ -100,155 +96,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -// used for cookies and such -func (a *AdformAdapter) Name() string { - return "adform" -} - -func (a *AdformAdapter) SkipNoCookies() bool { - return false -} - -func (a *AdformAdapter) Call(ctx context.Context, request *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - adformRequest, err := pbsRequestToAdformRequest(a, request, bidder) - if err != nil { - return nil, err - } - - uri := adformRequest.buildAdformUrl(a) - - debug := &pbs.BidderDebug{RequestURI: uri} - if request.IsDebug { - bidder.Debug = append(bidder.Debug, debug) - } - - httpRequest, err := http.NewRequest("GET", uri, nil) - if err != nil { - return nil, err - } - - httpRequest.Header = adformRequest.buildAdformHeaders(a) - - response, err := ctxhttp.Do(ctx, a.http.Client, httpRequest) - if err != nil { - return nil, err - } - - debug.StatusCode = response.StatusCode - - if response.StatusCode == 204 { - return nil, nil - } - - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - responseBody := string(body) - - if response.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", response.StatusCode, responseBody), - } - } - - if response.StatusCode != 200 { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", response.StatusCode, responseBody), - } - } - - if request.IsDebug { - debug.ResponseBody = responseBody - } - - adformBids, err := parseAdformBids(body) - if err != nil { - return nil, err - } - - bids := toPBSBidSlice(adformBids, adformRequest) - - return bids, nil -} - -func pbsRequestToAdformRequest(a *AdformAdapter, request *pbs.PBSRequest, bidder *pbs.PBSBidder) (*adformRequest, error) { - adUnits := make([]*adformAdUnit, 0, len(bidder.AdUnits)) - for _, adUnit := range bidder.AdUnits { - var adformAdUnit adformAdUnit - if err := json.Unmarshal(adUnit.Params, &adformAdUnit); err != nil { - return nil, err - } - mid, err := adformAdUnit.MasterTagId.Int64() - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - if mid <= 0 { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("master tag(placement) id is invalid=%s", adformAdUnit.MasterTagId), - } - } - adformAdUnit.bidId = adUnit.BidID - adformAdUnit.adUnitCode = adUnit.Code - adUnits = append(adUnits, &adformAdUnit) - } - - userId, _, _ := request.Cookie.GetUID(a.Name()) - - gdprApplies := request.ParseGDPR() - if gdprApplies != "0" && gdprApplies != "1" { - gdprApplies = "" - } - consent := request.ParseConsent() - - return &adformRequest{ - adUnits: adUnits, - ip: request.Device.IP, - advertisingId: request.Device.IFA, - userAgent: request.Device.UA, - bidderCode: bidder.BidderCode, - isSecure: request.Secure == 1, - referer: request.Url, - userId: userId, - tid: request.Tid, - gdprApplies: gdprApplies, - consent: consent, - currency: defaultCurrency, - }, nil -} - -func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice { - bids := make(pbs.PBSBidSlice, 0) - - for i, bid := range adformBids { - adm, bidType := getAdAndType(bid) - if adm == "" { - continue - } - pbsBid := pbs.PBSBid{ - BidID: r.adUnits[i].bidId, - AdUnitCode: r.adUnits[i].adUnitCode, - BidderCode: r.bidderCode, - Price: bid.Price, - Adm: adm, - Width: int64(bid.Width), - Height: int64(bid.Height), - DealId: bid.DealId, - Creative_id: bid.CreativeId, - CreativeMediaType: string(bidType), - } - - bids = append(bids, &pbsBid) - } - - return bids -} - -// COMMON - func (r *adformRequest) buildAdformUrl(a *AdformAdapter) string { parameters := url.Values{} @@ -359,20 +206,6 @@ func parseAdformBids(response []byte) ([]*adformBid, error) { // BIDDER Interface -func NewAdformLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpointURL string) *AdformAdapter { - var uriObj *url.URL - uriObj, err := url.Parse(endpointURL) - if err != nil { - panic(fmt.Sprintf("Incorrect Adform request url %s, check the configuration, please.", endpointURL)) - } - - return &AdformAdapter{ - http: adapters.NewHTTPAdapter(httpConfig), - URL: uriObj, - version: version, - } -} - func (a *AdformAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { adformRequest, errors := openRtbToAdformRequest(request) if len(adformRequest.adUnits) == 0 { diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index 53219f4c4c0..53a658f9715 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -2,26 +2,18 @@ package adform import ( "bytes" - "context" "encoding/json" + "fmt" "net/http" - "net/http/httptest" "strconv" "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters/adapterstest" - "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/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -71,31 +63,6 @@ type aBidInfo struct { buyerUID string secure bool currency string - delay time.Duration -} - -var adformTestData aBidInfo - -// Legacy auction tests - -func DummyAdformServer(w http.ResponseWriter, r *http.Request) { - errorString := assertAdformServerRequest(adformTestData, r, false) - if errorString != nil { - http.Error(w, *errorString, http.StatusInternalServerError) - return - } - - if adformTestData.delay > 0 { - <-time.After(adformTestData.delay) - } - - adformServerResponse, err := createAdformServerResponse(adformTestData) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(adformServerResponse) } func createAdformServerResponse(testData aBidInfo) ([]byte, error) { @@ -136,168 +103,6 @@ func createAdformServerResponse(testData aBidInfo) ([]byte, error) { return adformServerResponse, err } -func TestAdformBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyAdformServer)) - defer server.Close() - - adapter, ctx, prebidRequest := initTestData(server, t) - - bids, err := adapter.Call(ctx, prebidRequest, prebidRequest.Bidders[0]) - - if err != nil { - t.Fatalf("Should not have gotten adapter error: %v", err) - } - if len(bids) != 3 { - t.Fatalf("Received %d bids instead of 3", len(bids)) - } - expectedTypes := []openrtb_ext.BidType{ - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeVideo, - } - - for i, bid := range bids { - - if bid.CreativeMediaType != string(expectedTypes[i]) { - t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], bid.CreativeMediaType) - } - - matched := false - for _, tag := range adformTestData.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.BidderCode != "adform" { - t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode) - } - if bid.Price != tag.price { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) - } - if bid.Width != int64(adformTestData.width) || bid.Height != int64(adformTestData.height) { - t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, adformTestData.width, adformTestData.height) - } - if bid.Adm != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - } - if bid.DealId != tag.dealId { - t.Errorf("Incorrect deal id '%s' expected '%s'", bid.DealId, tag.dealId) - } - if bid.Creative_id != tag.creativeId { - t.Errorf("Incorrect creative id '%s' expected '%s'", bid.Creative_id, tag.creativeId) - } - } - } - if !matched { - t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - } - - // same test but with request timing out - adformTestData.delay = 5 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - bids, err = adapter.Call(ctx, prebidRequest, prebidRequest.Bidders[0]) - if err == nil { - t.Fatalf("Should have gotten a timeout error: %v", err) - } -} - -func initTestData(server *httptest.Server, t *testing.T) (*AdformAdapter, context.Context, *pbs.PBSRequest) { - adformTestData = createTestData(false) - - // prepare adapter - conf := *adapters.DefaultHTTPAdapterConfig - adapter := NewAdformLegacyAdapter(&conf, server.URL) - - prebidRequest := preparePrebidRequest(server.URL, t) - ctx := context.TODO() - - return adapter, ctx, prebidRequest -} - -func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { - body := preparePrebidRequestBody(adformTestData, t) - prebidHttpRequest := httptest.NewRequest("POST", serverUrl, body) - prebidHttpRequest.Header.Add("User-Agent", adformTestData.deviceUA) - prebidHttpRequest.Header.Add("Referer", adformTestData.referrer) - prebidHttpRequest.Header.Add("X-Real-IP", adformTestData.deviceIP) - - pbsCookie := usersync.ParseCookieFromRequest(prebidHttpRequest, &config.HostCookie{}) - pbsCookie.TrySync("adform", adformTestData.buyerUID) - fakeWriter := httptest.NewRecorder() - - pbsCookie.SetCookieOnResponse(fakeWriter, false, &config.HostCookie{Domain: ""}, time.Minute) - prebidHttpRequest.Header.Add("Cookie", fakeWriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - r, err := pbs.ParsePBSRequest(prebidHttpRequest, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &config.HostCookie{}) - if err != nil { - t.Fatalf("ParsePBSRequest failed: %v", err) - } - if len(r.Bidders) != 1 { - t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(r.Bidders)) - } - if r.Bidders[0].BidderCode != "adform" { - t.Fatalf("ParsePBSRequest returned invalid bidder") - } - - // can't be set in preparePrebidRequestBody as will be lost during json serialization and deserialization - // for the adapters which don't support OpenRTB requests the old PBSRequest is created from OpenRTB request - // so User and Regs are copied from OpenRTB request, see legacy.go -> toLegacyRequest - regs := getRegs() - r.Regs = ®s - user := openrtb2.User{ - Ext: getUserExt(), - } - r.User = &user - - return r -} - -func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer { - prebidRequest := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 4), - Device: &openrtb2.Device{ - UA: requestData.deviceUA, - IP: requestData.deviceIP, - IFA: requestData.deviceIFA, - }, - Tid: requestData.tid, - Secure: 0, - } - for i, tag := range requestData.tags { - prebidRequest.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - Sizes: []openrtb2.Format{ - { - W: int64(requestData.width), - H: int64(requestData.height), - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "adform", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: json.RawMessage(formatAdUnitJson(tag)), - }, - }, - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(prebidRequest) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - fmt.Println("body", body) - return body -} - -// OpenRTB auction tests - func TestOpenRTBRequest(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ Endpoint: "https://adx.adform.net"}) @@ -324,7 +129,7 @@ func TestOpenRTBRequest(t *testing.T) { } r.Header = httpRequests[0].Headers - errorString := assertAdformServerRequest(testData, r, true) + errorString := assertAdformServerRequest(testData, r) if errorString != nil { t.Errorf("Request error: %s", *errorString) } @@ -499,16 +304,6 @@ func TestOpenRTBSurpriseResponse(t *testing.T) { } } -// Properties tests - -func TestAdformProperties(t *testing.T) { - adapter := NewAdformLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "adx.adform.net/adx") - - if adapter.SkipNoCookies() != false { - t.Fatalf("should have been false") - } -} - // helpers func getRegs() openrtb2.Regs { @@ -588,7 +383,7 @@ func formatAdUnitParam(fieldName string, fieldValue string) string { return "" } -func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb bool) *string { +func assertAdformServerRequest(testData aBidInfo, r *http.Request) *string { if ok, err := equal("GET", r.Method, "HTTP method"); !ok { return err } @@ -598,15 +393,8 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb boo } } - var midsWithCurrency = "" - var queryString = "" - if isOpenRtb { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS" - queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency - } else { - midsWithCurrency = "bWlkPTMyMzQ0JnJjdXI9VVNEJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9VVNEJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9VVNE&bWlkPTMyMzQ3JnJjdXI9VVNE" // no way to pass currency in legacy adapter - queryString = "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&" + midsWithCurrency - } + midsWithCurrency := "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS" + queryString := "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency if ok, err := equal(queryString, r.URL.RawQuery, "Query string"); !ok { return err diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index 6fc12e3df5e..b23b49b3774 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -109,7 +109,7 @@ func (a *AdheseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap // Compose url endpointParams := macros.EndpointTemplateParams{AccountID: params.Account} - host, err := macros.ResolveMacros(*&a.endpointTemplate, endpointParams) + host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) if err != nil { errs = append(errs, WrapReqError("Could not compose url from template and request account val: "+err.Error())) return nil, errs diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index 808951d3aba..fd31bc9d14f 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -25,10 +25,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -type admanParams struct { - TagID string `json:"TagID"` -} - // MakeRequests create bid request for adman demand func (a *AdmanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index b1004601774..3695f541532 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -1,8 +1,6 @@ package appnexus import ( - "bytes" - "context" "encoding/json" "errors" "fmt" @@ -15,9 +13,6 @@ import ( "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - - "golang.org/x/net/context/ctxhttp" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" @@ -27,22 +22,12 @@ import ( const defaultPlatformID int = 5 -type AppNexusAdapter struct { - http *adapters.HTTPAdapter +type adapter struct { URI string iabCategoryMap map[string]string hbSource int } -// used for cookies and such -func (a *AppNexusAdapter) Name() string { - return "adnxs" -} - -func (a *AppNexusAdapter) SkipNoCookies() bool { - return false -} - type KeyVal struct { Key string `json:"key,omitempty"` Values []string `json:"value,omitempty"` @@ -52,21 +37,6 @@ type appnexusAdapterOptions struct { IabCategories map[string]string `json:"iab_categories"` } -type appnexusParams struct { - LegacyPlacementId int `json:"placementId"` - LegacyInvCode string `json:"invCode"` - LegacyTrafficSourceCode string `json:"trafficSourceCode"` - PlacementId int `json:"placement_id"` - InvCode string `json:"inv_code"` - Member string `json:"member"` - Keywords []KeyVal `json:"keywords"` - TrafficSourceCode string `json:"traffic_source_code"` - Reserve float64 `json:"reserve"` - Position string `json:"position"` - UsePmtRule *bool `json:"use_pmt_rule"` - PrivateSizes json.RawMessage `json:"private_sizes"` -} - type appnexusImpExtAppnexus struct { PlacementID int `json:"placement_id,omitempty"` Keywords string `json:"keywords,omitempty"` @@ -115,181 +85,7 @@ type appnexusReqExt struct { var maxImpsPerReq = 10 -func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - anReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), supportedMediaTypes) - - if err != nil { - return nil, err - } - uri := a.URI - for i, unit := range bidder.AdUnits { - var params appnexusParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, err - } - // Accept legacy Appnexus parameters if we don't have modern ones - // Don't worry if both is set as validation rules should prevent, and this is temporary anyway. - if params.PlacementId == 0 && params.LegacyPlacementId != 0 { - params.PlacementId = params.LegacyPlacementId - } - if params.InvCode == "" && params.LegacyInvCode != "" { - params.InvCode = params.LegacyInvCode - } - if params.TrafficSourceCode == "" && params.LegacyTrafficSourceCode != "" { - params.TrafficSourceCode = params.LegacyTrafficSourceCode - } - - if params.PlacementId == 0 && (params.InvCode == "" || params.Member == "") { - return nil, &errortypes.BadInput{ - Message: "No placement or member+invcode provided", - } - } - - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(anReq.Imp) <= i { - break - } - if params.InvCode != "" { - anReq.Imp[i].TagID = params.InvCode - if params.Member != "" { - // this assumes that the same member ID is used across all tags, which should be the case - uri = appendMemberId(a.URI, params.Member) - } - - } - if params.Reserve > 0 { - anReq.Imp[i].BidFloor = params.Reserve // TODO: we need to factor in currency here if non-USD - } - if anReq.Imp[i].Banner != nil && params.Position != "" { - if params.Position == "above" { - anReq.Imp[i].Banner.Pos = openrtb2.AdPositionAboveTheFold.Ptr() - } else if params.Position == "below" { - anReq.Imp[i].Banner.Pos = openrtb2.AdPositionBelowTheFold.Ptr() - } - } - - kvs := make([]string, 0, len(params.Keywords)*2) - for _, kv := range params.Keywords { - if len(kv.Values) == 0 { - kvs = append(kvs, kv.Key) - } else { - for _, val := range kv.Values { - kvs = append(kvs, fmt.Sprintf("%s=%s", kv.Key, val)) - } - - } - } - - keywordStr := strings.Join(kvs, ",") - - impExt := appnexusImpExt{Appnexus: appnexusImpExtAppnexus{ - PlacementID: params.PlacementId, - TrafficSourceCode: params.TrafficSourceCode, - Keywords: keywordStr, - UsePmtRule: params.UsePmtRule, - PrivateSizes: params.PrivateSizes, - }} - anReq.Imp[i].Ext, err = json.Marshal(&impExt) - } - - reqJSON, err := json.Marshal(anReq) - if err != nil { - return nil, err - } - - debug := &pbs.BidderDebug{ - RequestURI: uri, - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", uri, bytes.NewBuffer(reqJSON)) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - anResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - debug.StatusCode = anResp.StatusCode - - if anResp.StatusCode == 204 { - return nil, nil - } - - defer anResp.Body.Close() - body, err := ioutil.ReadAll(anResp.Body) - if err != nil { - return nil, err - } - responseBody := string(body) - - if anResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, responseBody), - } - } - - if anResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, responseBody), - } - } - - if req.IsDebug { - debug.ResponseBody = responseBody - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, err - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - NURL: bid.NURL, - } - - var impExt appnexusBidExt - if err := json.Unmarshal(bid.Ext, &impExt); err == nil { - if mediaType, err := getMediaTypeForBid(&impExt); err == nil { - pbid.CreativeMediaType = string(mediaType) - bids = append(bids, &pbid) - } - } - } - } - - return bids, nil -} - -func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { memberIds := make(map[string]bool) errs := make([]error, 0, len(request.Imp)) @@ -383,7 +179,7 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad requests := make([]*adapters.RequestData, 0, len(podImps)) for _, podImps := range podImps { - reqExt.Appnexus.AdPodId = generatePodId() + reqExt.Appnexus.AdPodId = generatePodID() reqs, errors := splitRequests(podImps, request, reqExt, thisURI, errs) requests = append(requests, reqs...) @@ -395,7 +191,7 @@ func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad return splitRequests(imps, request, reqExt, thisURI, errs) } -func generatePodId() string { +func generatePodID() string { val := rand.Int63() return fmt.Sprint(val) } @@ -561,7 +357,7 @@ func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string { return strings.Join(kvs, ",") } -func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []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 } @@ -596,7 +392,7 @@ func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa bid.Cat = []string{iabCategory} } else if len(bid.Cat) > 1 { //create empty categories array to force bid to be rejected - bid.Cat = make([]string, 0, 0) + bid.Cat = make([]string, 0) } impVideo := &openrtb_ext.ExtBidPrebidVideo{ @@ -638,7 +434,7 @@ func getMediaTypeForBid(bid *appnexusBidExt) (openrtb_ext.BidType, error) { } // getIabCategoryForBid maps an appnexus brand id to an IAB category. -func (a *AppNexusAdapter) getIabCategoryForBid(bid *appnexusBidExt) (string, error) { +func (a *adapter) getIabCategoryForBid(bid *appnexusBidExt) (string, error) { brandIDString := strconv.Itoa(bid.Appnexus.BrandCategory) if iabCategory, ok := a.iabCategoryMap[brandIDString]; ok { return iabCategory, nil @@ -657,7 +453,7 @@ func appendMemberId(uri string, memberId string) string { // Builder builds a new instance of the AppNexus adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - bidder := &AppNexusAdapter{ + bidder := &adapter{ URI: config.Endpoint, iabCategoryMap: loadCategoryMapFromFileSystem(), hbSource: resolvePlatformID(config.PlatformID), @@ -665,16 +461,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -// NewAppNexusLegacyAdapter builds a legacy version of the AppNexus adapter. -func NewAppNexusLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpoint, platformID string) *AppNexusAdapter { - return &AppNexusAdapter{ - http: adapters.NewHTTPAdapter(httpConfig), - URI: endpoint, - iabCategoryMap: loadCategoryMapFromFileSystem(), - hbSource: resolvePlatformID(platformID), - } -} - func resolvePlatformID(platformID string) int { if len(platformID) > 0 { if val, err := strconv.Atoi(platformID); err == nil { diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index cad52348134..6399be1a5bb 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -1,29 +1,17 @@ package appnexus import ( - "bytes" - "context" "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" "regexp" "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/stretchr/testify/assert" - - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "fmt" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/stretchr/testify/assert" ) func TestJsonSamples(t *testing.T) { @@ -58,7 +46,7 @@ func TestMemberQueryParam(t *testing.T) { } func TestVideoSinglePod(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -96,7 +84,7 @@ func TestVideoSinglePod(t *testing.T) { } func TestVideoSinglePodManyImps(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -154,7 +142,7 @@ func TestVideoSinglePodManyImps(t *testing.T) { } func TestVideoTwoPods(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -206,7 +194,7 @@ func TestVideoTwoPods(t *testing.T) { } func TestVideoTwoPodsManyImps(t *testing.T) { - var a AppNexusAdapter + var a adapter a.URI = "http://test.com/openrtb2" a.hbSource = 5 @@ -281,396 +269,3 @@ func TestVideoTwoPodsManyImps(t *testing.T) { assert.Len(t, podIds, 2, "Incorrect number of unique pod ids") } - -// ---------------------------------------------------------------------------- -// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb2. - -type anTagInfo struct { - code string - invCode string - placementID int - trafficSourceCode string - in_keywords string - out_keywords string - reserve float64 - position string - bid float64 - content string - mediaType string -} - -type anBidInfo struct { - memberID string - domain string - page string - accountID int - siteID int - tags []anTagInfo - deviceIP string - deviceUA string - buyerUID string - delay time.Duration -} - -var andata anBidInfo - -func DummyAppNexusServer(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 - } - - memberID := r.FormValue("member_id") - if memberID != andata.memberID { - http.Error(w, fmt.Sprintf("Member ID '%s' doesn't match '%s", memberID, andata.memberID), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - ID: breq.ID, - BidID: "a-random-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "Buyer Member ID", - Bid: make([]openrtb2.Bid, 0, 2), - }, - }, - } - - for i, imp := range breq.Imp { - var aix appnexusImpExt - err = json.Unmarshal(imp.Ext, &aix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Either placementID or member+invCode must be specified - has_placement := false - if aix.Appnexus.PlacementID != 0 { - if aix.Appnexus.PlacementID != andata.tags[i].placementID { - http.Error(w, fmt.Sprintf("Placement ID '%d' doesn't match '%d", aix.Appnexus.PlacementID, - andata.tags[i].placementID), http.StatusInternalServerError) - return - } - has_placement = true - } - if memberID != "" && imp.TagID != "" { - if imp.TagID != andata.tags[i].invCode { - http.Error(w, fmt.Sprintf("Inv Code '%s' doesn't match '%s", imp.TagID, - andata.tags[i].invCode), http.StatusInternalServerError) - return - } - has_placement = true - } - if !has_placement { - http.Error(w, fmt.Sprintf("Either placement or member+inv not present"), http.StatusInternalServerError) - return - } - - if aix.Appnexus.Keywords != andata.tags[i].out_keywords { - http.Error(w, fmt.Sprintf("Keywords '%s' doesn't match '%s", aix.Appnexus.Keywords, - andata.tags[i].out_keywords), http.StatusInternalServerError) - return - } - - if aix.Appnexus.TrafficSourceCode != andata.tags[i].trafficSourceCode { - http.Error(w, fmt.Sprintf("Traffic source code '%s' doesn't match '%s", aix.Appnexus.TrafficSourceCode, - andata.tags[i].trafficSourceCode), http.StatusInternalServerError) - return - } - if imp.BidFloor != andata.tags[i].reserve { - http.Error(w, fmt.Sprintf("Bid floor '%.2f' doesn't match '%.2f", imp.BidFloor, - andata.tags[i].reserve), http.StatusInternalServerError) - return - } - if imp.Banner == nil && imp.Video == nil { - http.Error(w, fmt.Sprintf("No banner or app object sent"), http.StatusInternalServerError) - return - } - if (imp.Banner == nil && andata.tags[i].mediaType == "banner") || (imp.Banner != nil && andata.tags[i].mediaType != "banner") { - http.Error(w, fmt.Sprintf("Invalid impression type - banner"), http.StatusInternalServerError) - return - } - if (imp.Video == nil && andata.tags[i].mediaType == "video") || (imp.Video != nil && andata.tags[i].mediaType != "video") { - http.Error(w, fmt.Sprintf("Invalid impression type - video"), http.StatusInternalServerError) - return - } - - if imp.Banner != nil { - if len(imp.Banner.Format) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.banner.format array"), http.StatusInternalServerError) - return - } - if andata.tags[i].position == "above" && *imp.Banner.Pos != openrtb2.AdPosition(1) { - http.Error(w, fmt.Sprintf("Mismatch in position - expected 1 for atf"), http.StatusInternalServerError) - return - } - if andata.tags[i].position == "below" && *imp.Banner.Pos != openrtb2.AdPosition(3) { - http.Error(w, fmt.Sprintf("Mismatch in position - expected 3 for btf"), http.StatusInternalServerError) - return - } - } - if imp.Video != nil { - // TODO: add more validations - if len(imp.Video.MIMEs) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.mimes array"), http.StatusInternalServerError) - return - } - if len(imp.Video.Protocols) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.protocols array"), http.StatusInternalServerError) - return - } - for _, protocol := range imp.Video.Protocols { - if protocol < 1 || protocol > 8 { - http.Error(w, fmt.Sprintf("Invalid video protocol %d", protocol), http.StatusInternalServerError) - return - } - } - } - - resBid := openrtb2.Bid{ - ID: "random-id", - ImpID: imp.ID, - Price: andata.tags[i].bid, - AdM: andata.tags[i].content, - Ext: json.RawMessage(fmt.Sprintf(`{"appnexus":{"bid_ad_type":%d}}`, bidTypeToInt(andata.tags[i].mediaType))), - } - - if imp.Video != nil { - resBid.Attr = []openrtb2.CreativeAttribute{openrtb2.CreativeAttribute(6)} - } - resp.SeatBid[0].Bid = append(resp.SeatBid[0].Bid, resBid) - } - - // TODO: are all of these valid for app? - if breq.Site == nil { - http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) - return - } - if breq.Site.Domain != andata.domain { - http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, andata.domain), http.StatusInternalServerError) - return - } - if breq.Site.Page != andata.page { - http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, andata.page), http.StatusInternalServerError) - return - } - if breq.Device.UA != andata.deviceUA { - http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s", breq.Device.UA, andata.deviceUA), http.StatusInternalServerError) - return - } - if breq.Device.IP != andata.deviceIP { - http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s", breq.Device.IP, andata.deviceIP), http.StatusInternalServerError) - return - } - if breq.User.BuyerUID != andata.buyerUID { - http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s", breq.User.BuyerUID, andata.buyerUID), http.StatusInternalServerError) - return - } - - if andata.delay > 0 { - <-time.After(andata.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 bidTypeToInt(bidType string) int { - switch bidType { - case "banner": - return 0 - case "video": - return 1 - case "audio": - return 2 - case "native": - return 3 - default: - return -1 - } -} -func TestAppNexusLegacyBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyAppNexusServer)) - defer server.Close() - - andata = anBidInfo{ - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - tags: make([]anTagInfo, 2), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "23482348223", - memberID: "958", - } - andata.tags[0] = anTagInfo{ - code: "first-tag", - placementID: 8394, - bid: 1.67, - trafficSourceCode: "ppc-exchange", - content: "huh", - in_keywords: "[{ \"key\": \"genre\", \"value\": [\"jazz\", \"pop\"] }, {\"key\": \"myEmptyKey\", \"value\": []}]", - out_keywords: "genre=jazz,genre=pop,myEmptyKey", - reserve: 1.50, - position: "below", - mediaType: "banner", - } - andata.tags[1] = anTagInfo{ - code: "second-tag", - invCode: "leftbottom", - bid: 3.22, - trafficSourceCode: "taboola", - content: "yow!", - in_keywords: "[{ \"key\": \"genre\", \"value\": [\"rock\", \"pop\"] }, {\"key\": \"myKey\", \"value\": [\"myVal\"]}]", - out_keywords: "genre=rock,genre=pop,myKey=myVal", - reserve: 0.75, - position: "above", - mediaType: "video", - } - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewAppNexusLegacyAdapter(&conf, server.URL, "") - - pbin := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 2), - } - for i, tag := range andata.tags { - var params json.RawMessage - if tag.placementID > 0 { - params = json.RawMessage(fmt.Sprintf("{\"placementId\": %d, \"member\": \"%s\", \"keywords\": %s, "+ - "\"trafficSourceCode\": \"%s\", \"reserve\": %.3f, \"position\": \"%s\"}", - tag.placementID, andata.memberID, tag.in_keywords, tag.trafficSourceCode, tag.reserve, tag.position)) - } else { - params = json.RawMessage(fmt.Sprintf("{\"invCode\": \"%s\", \"member\": \"%s\", \"keywords\": %s, "+ - "\"trafficSourceCode\": \"%s\", \"reserve\": %.3f, \"position\": \"%s\"}", - tag.invCode, andata.memberID, tag.in_keywords, tag.trafficSourceCode, tag.reserve, tag.position)) - } - - pbin.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 600, - }, - { - W: 300, - H: 250, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "appnexus", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: params, - }, - }, - } - if tag.mediaType == "video" { - pbin.AdUnits[i].Video = pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{2, 3}, - } - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbin) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - - req := httptest.NewRequest("POST", server.URL, body) - req.Header.Add("Referer", andata.page) - req.Header.Add("User-Agent", andata.deviceUA) - req.Header.Add("X-Real-IP", andata.deviceIP) - - pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) - pc.TrySync("adnxs", andata.buyerUID) - 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 != "appnexus" { - 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 andata.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.CreativeMediaType != tag.mediaType { - t.Errorf("Incorrect Creative MediaType '%s'. Expected '%s'", bid.CreativeMediaType, tag.mediaType) - } - if bid.BidderCode != "appnexus" { - 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.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 - andata.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/connectad/connectad.go b/adapters/connectad/connectad.go index 9827ebcea7b..5c30e3a6adc 100644 --- a/adapters/connectad/connectad.go +++ b/adapters/connectad/connectad.go @@ -18,10 +18,6 @@ type ConnectAdAdapter struct { endpoint string } -type connectadImpExt struct { - ConnectAd openrtb_ext.ExtImpConnectAd `json:"connectad"` -} - // Builder builds a new instance of the ConnectAd adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &ConnectAdAdapter{ diff --git a/adapters/consumable/instant.go b/adapters/consumable/instant.go index 5a32fef8837..a6162d44e22 100644 --- a/adapters/consumable/instant.go +++ b/adapters/consumable/instant.go @@ -9,7 +9,7 @@ type instant interface { // Send a real instance when you construct it in adapter_map.go type realInstant struct{} -func (_ realInstant) Now() time.Time { +func (realInstant) Now() time.Time { return time.Now() } diff --git a/adapters/consumable/utils.go b/adapters/consumable/utils.go deleted file mode 100644 index 64e4872c619..00000000000 --- a/adapters/consumable/utils.go +++ /dev/null @@ -1,20 +0,0 @@ -package consumable - -import ( - netUrl "net/url" -) - -/** - * Creates a snippet of HTML that retrieves the specified `url` - * Returns HTML snippet that contains the img src = set to `url` - */ -func createTrackPixelHtml(url *string) string { - if url == nil { - return "" - } - - escapedUrl := netUrl.QueryEscape(*url) - img := "
" + - "
" - return img -} diff --git a/adapters/conversant/cnvr_legacy.go b/adapters/conversant/cnvr_legacy.go deleted file mode 100644 index eff1afc5d32..00000000000 --- a/adapters/conversant/cnvr_legacy.go +++ /dev/null @@ -1,291 +0,0 @@ -package conversant - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "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 ConversantLegacyAdapter struct { - http *adapters.HTTPAdapter - URI string -} - -// Corresponds to the bidder name in cookies and requests -func (a *ConversantLegacyAdapter) Name() string { - return "conversant" -} - -// Return true so no request will be sent unless user has been sync'ed. -func (a *ConversantLegacyAdapter) SkipNoCookies() bool { - return true -} - -type conversantParams struct { - SiteID string `json:"site_id"` - Secure *int8 `json:"secure"` - TagID string `json:"tag_id"` - Position *int8 `json:"position"` - BidFloor float64 `json:"bidfloor"` - Mobile *int8 `json:"mobile"` - MIMEs []string `json:"mimes"` - API []int8 `json:"api"` - Protocols []int8 `json:"protocols"` - MaxDuration *int64 `json:"maxduration"` -} - -func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - cnvrReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - return nil, err - } - - // Create a map of impression objects for both request creation - // and response parsing. - - impMap := make(map[string]*openrtb2.Imp, len(cnvrReq.Imp)) - for idx := range cnvrReq.Imp { - impMap[cnvrReq.Imp[idx].ID] = &cnvrReq.Imp[idx] - } - - // Fill in additional info from custom params - - for _, unit := range bidder.AdUnits { - var params conversantParams - - imp := impMap[unit.Code] - if imp == nil { - // Skip ad units that do not have corresponding impressions. - continue - } - - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - - // Fill in additional Site info - if params.SiteID != "" { - if cnvrReq.Site != nil { - cnvrReq.Site.ID = params.SiteID - } - if cnvrReq.App != nil { - cnvrReq.App.ID = params.SiteID - } - } - - if params.Mobile != nil && !(cnvrReq.Site == nil) { - cnvrReq.Site.Mobile = *params.Mobile - } - - // Fill in additional impression info - - imp.DisplayManager = "prebid-s2s" - imp.DisplayManagerVer = "1.0.1" - imp.BidFloor = params.BidFloor - imp.TagID = params.TagID - - var position *openrtb2.AdPosition - if params.Position != nil { - position = openrtb2.AdPosition(*params.Position).Ptr() - } - - if imp.Banner != nil { - imp.Banner.Pos = position - } else if imp.Video != nil { - imp.Video.Pos = position - - if len(params.API) > 0 { - imp.Video.API = make([]openrtb2.APIFramework, 0, len(params.API)) - for _, api := range params.API { - imp.Video.API = append(imp.Video.API, openrtb2.APIFramework(api)) - } - } - - // Include protocols, mimes, and max duration if specified - // These properties can also be specified in ad unit's video object, - // but are overridden if the custom params object also contains them. - - if len(params.Protocols) > 0 { - imp.Video.Protocols = make([]openrtb2.Protocol, 0, len(params.Protocols)) - for _, protocol := range params.Protocols { - imp.Video.Protocols = append(imp.Video.Protocols, openrtb2.Protocol(protocol)) - } - } - - if len(params.MIMEs) > 0 { - imp.Video.MIMEs = make([]string, len(params.MIMEs)) - copy(imp.Video.MIMEs, params.MIMEs) - } - - if params.MaxDuration != nil { - imp.Video.MaxDuration = *params.MaxDuration - } - } - - // Take care not to override the global secure flag - - if (imp.Secure == nil || *imp.Secure == 0) && params.Secure != nil { - imp.Secure = params.Secure - } - } - - // Do a quick check on required parameters - - if cnvrReq.Site != nil && cnvrReq.Site.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing site id", - } - } - - if cnvrReq.App != nil && cnvrReq.App.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing app id", - } - } - - // Start capturing debug info - - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if cnvrReq.Device == nil { - cnvrReq.Device = &openrtb2.Device{} - } - - // Convert request to json to be sent over http - - j, _ := json.Marshal(cnvrReq) - - if req.IsDebug { - debug.RequestBody = string(j) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(j)) - httpReq.Header.Add("Content-Type", "application/json") - httpReq.Header.Add("Accept", "application/json") - - resp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if req.IsDebug { - debug.StatusCode = resp.StatusCode - } - - if resp.StatusCode == 204 { - return nil, nil - } - - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } - } - - if resp.StatusCode != 200 { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } - } - - if req.IsDebug { - debug.ResponseBody = string(body) - } - - var bidResp openrtb2.BidResponse - - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, seatbid := range bidResp.SeatBid { - for _, bid := range seatbid.Bid { - if bid.Price <= 0 { - continue - } - - imp := impMap[bid.ImpID] - if imp == nil { - // All returned bids should have a matching impression - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression id '%s'", bid.ImpID), - } - } - - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbsBid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - Price: bid.Price, - Creative_id: bid.CrID, - BidderCode: bidder.BidderCode, - } - - if imp.Video != nil { - pbsBid.CreativeMediaType = "video" - pbsBid.NURL = bid.AdM // Assign to NURL so it'll be interpreted as a vastUrl - pbsBid.Width = imp.Video.W - pbsBid.Height = imp.Video.H - } else { - pbsBid.CreativeMediaType = "banner" - pbsBid.NURL = bid.NURL - pbsBid.Adm = bid.AdM - pbsBid.Width = bid.W - pbsBid.Height = bid.H - } - - bids = append(bids, &pbsBid) - } - } - - if len(bids) == 0 { - return nil, nil - } - - return bids, nil -} - -func NewConversantLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *ConversantLegacyAdapter { - a := adapters.NewHTTPAdapter(config) - - return &ConversantLegacyAdapter{ - http: a, - URI: uri, - } -} diff --git a/adapters/conversant/cnvr_legacy_test.go b/adapters/conversant/cnvr_legacy_test.go deleted file mode 100644 index fc34a93fae2..00000000000 --- a/adapters/conversant/cnvr_legacy_test.go +++ /dev/null @@ -1,853 +0,0 @@ -package conversant - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" -) - -// Constants - -const ExpectedSiteID string = "12345" -const ExpectedDisplayManager string = "prebid-s2s" -const ExpectedBuyerUID string = "AQECT_o7M1FLbQJK8QFmAQEBAQE" -const ExpectedNURL string = "http://test.dotomi.com" -const ExpectedAdM string = "" -const ExpectedCrID string = "98765" - -const DefaultParam = `{"site_id": "12345"}` - -// Test properties of Adapter interface - -func TestConversantProperties(t *testing.T) { - an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - assertNotEqual(t, an.Name(), "", "Missing family name") - assertTrue(t, an.SkipNoCookies(), "SkipNoCookies should be true") -} - -// Test empty bid requests - -func TestConversantEmptyBid(t *testing.T) { - an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := an.Call(ctx, &pbReq, &pbBidder) - assertTrue(t, err != nil, "No error received for an invalid request") -} - -// Test required parameters, which is just the site id for now - -func TestConversantRequiredParameters(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - an := NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - ctx := context.TODO() - - testParams := func(params ...string) (pbs.PBSBidSlice, error) { - req, err := CreateBannerRequest(params...) - if err != nil { - return nil, err - } - return an.Call(ctx, req, req.Bidders[0]) - } - - var err error - - if _, err = testParams(`{}`); err == nil { - t.Fatal("Failed to catch missing site id") - } -} - -// Test handling of 404 - -func TestConversantBadStatus(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assertTrue(t, err != nil, "Failed to catch 404 error") -} - -// Test handling of HTTP timeout - -func TestConversantTimeout(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - // Create a context that expires before http returns - - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - // Create a basic request - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - // Attempt to process the request, which should hit a timeout - // immediately - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err == nil || err != context.DeadlineExceeded { - t.Fatal("No timeout recevied for timed out request", err) - } -} - -// Test handling of 204 - -func TestConversantNoBid(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if resp != nil || err != nil { - t.Fatal("Failed to handle empty bid", err) - } -} - -// Verify an outgoing openrtp request is created correctly - -func TestConversantRequestDefault(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, imp.TagID, "", "Request tag id") - assertTrue(t, imp.Banner.Pos == nil, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify inapp video request -func TestConversantInappVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - requestParam := `{"secure": 1, "site_id": "12345"}` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(requestParam) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - assertEqual(t, lastReq.App.ID, "12345", "App Id") -} - -// Verify inapp video request -func TestConversantInappBannerRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "secure": 1, - "site_id": "12345", - "tag_id": "top", - "position": 2, - "bidfloor": 1.01 }` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - - ctx := context.TODO() - pbReq, _ := CreateBannerRequest(param) - pbReq, err := ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, lastReq.App.ID, "12345", "App Id") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify an outgoing openrtp request with additional conversant parameters is -// processed correctly - -func TestConversantRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile": 1 }` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 1, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 1, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "top", "Request tag id") - assertEqual(t, int(*imp.Banner.Pos), 2, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify openrtp responses are converted correctly - -func TestConversantResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, ExpectedAdM, "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedNURL, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, *imps[i].Banner.W, "Bad width in response") - assertEqual(t, bid.Height, *imps[i].Banner.H, "Bad height in response") - } - } -} - -// Test video request - -func TestConversantBasicVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/mp4", "Requst video MIMEs type") - assertTrue(t, imp.Video.Protocols == nil, "Request video protocols") - assertEqual(t, imp.Video.MaxDuration, int64(0), "Request video 0 max duration") - assertTrue(t, imp.Video.API == nil, "Request video api should be nil") -} - -// Test video request with parameters in custom params object - -func TestConversantVideoRequestWithParams(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01, - "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "api": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") - assertEqual(t, len(imp.Video.API), 2, "Request video api should be nil") - assertEqual(t, imp.Video.API[0], openrtb2.APIFramework(1), "Request video api 1") - assertEqual(t, imp.Video.API[1], openrtb2.APIFramework(2), "Request video api 2") -} - -// Test video request with parameters in the video object - -func TestConversantVideoRequestWithParams2(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(param) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("Failed to convert to a video request", err) - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("Failed to parse video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb2.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb2.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") -} - -// Test video responses - -func TestConversantVideoResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantLegacyAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, "", "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedAdM, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, imps[i].Video.W, "Bad width in response") - assertEqual(t, bid.Height, imps[i].Video.H, "Bad height in response") - } - } -} - -// Helpers to create a banner and video requests - -func CreateRequest(params ...string) *pbs.PBSRequest { - num := len(params) - - req := pbs.PBSRequest{ - Tid: "t-000", - AccountID: "1", - AdUnits: make([]pbs.AdUnit, num), - } - - for i := 0; i < num; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("au-%03d", i), - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "conversant", - BidID: fmt.Sprintf("b-%03d", i), - Params: json.RawMessage(params[i]), - }, - }, - } - } - - return &req -} - -// Convert a request to a video request by adding required properties - -func ConvertToVideoRequest(req *pbs.PBSRequest, videoParams ...string) (*pbs.PBSRequest, error) { - for i := 0; i < len(req.AdUnits); i++ { - video := pbs.PBSVideo{} - if i < len(videoParams) { - err := json.Unmarshal([]byte(videoParams[i]), &video) - if err != nil { - return nil, err - } - } - - if video.Mimes == nil { - video.Mimes = []string{"video/mp4"} - } - - req.AdUnits[i].Video = video - req.AdUnits[i].MediaTypes = []string{"video"} - } - - return req, nil -} - -// Convert a request to an app request by adding required properties -func ConvertToAppRequest(req *pbs.PBSRequest, appParams string) (*pbs.PBSRequest, error) { - app := new(openrtb2.App) - err := json.Unmarshal([]byte(appParams), &app) - if err == nil { - req.App = app - } - - return req, nil -} - -// Feed the request thru the prebid parser so user id and -// other private properties are defined - -func ParseRequest(req *pbs.PBSRequest) (*pbs.PBSRequest, error) { - body := new(bytes.Buffer) - _ = json.NewEncoder(body).Encode(req) - - // Need to pass the conversant user id thru uid cookie - - httpReq := httptest.NewRequest("POST", "/foo", body) - cookie := usersync.NewCookie() - _ = cookie.TrySync("conversant", ExpectedBuyerUID) - httpReq.Header.Set("Cookie", cookie.ToHTTPCookie(90*24*time.Hour).String()) - httpReq.Header.Add("Referer", "http://example.com") - cache, _ := dummycache.New() - hcc := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cache, &hcc) - - return parsedReq, err -} - -// A helper to create a banner request - -func CreateBannerRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ParseRequest(req) - return req, err -} - -// A helper to create a video request - -func CreateVideoRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ConvertToVideoRequest(req) - if err != nil { - return nil, err - } - req, err = ParseRequest(req) - return req, err -} - -// Helper to create a test http server that receives and generate openrtb requests and responses - -func CreateServer(prices ...float64) (*httptest.Server, *openrtb2.BidRequest) { - var lastBidRequest openrtb2.BidRequest - server := httptest.NewServer(http.HandlerFunc(func(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 bidReq openrtb2.BidRequest - var price float64 - var bids []openrtb2.Bid - var bid openrtb2.Bid - - err = json.Unmarshal(body, &bidReq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - lastBidRequest = bidReq - - for i, imp := range bidReq.Imp { - if i < len(prices) { - price = prices[i] - } else { - price = 0 - } - - if price > 0 { - bid = openrtb2.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: price, - NURL: ExpectedNURL, - AdM: ExpectedAdM, - CrID: ExpectedCrID, - } - - if imp.Banner != nil { - bid.W = *imp.Banner.W - bid.H = *imp.Banner.H - } else if imp.Video != nil { - bid.W = imp.Video.W - bid.H = imp.Video.H - } - } else { - bid = openrtb2.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: 0, - } - } - - bids = append(bids, bid) - } - - if len(bids) == 0 { - w.WriteHeader(http.StatusNoContent) - } else { - js, _ := json.Marshal(openrtb2.BidResponse{ - ID: bidReq.ID, - SeatBid: []openrtb2.SeatBid{ - { - Bid: bids, - }, - }, - }) - - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write(js) - } - }), - ) - - return server, &lastBidRequest -} - -// Helper to remove impressions with $0 bids - -func FilterZeroPrices(prices []float64, imps []openrtb2.Imp) ([]float64, []openrtb2.Imp) { - prices2 := make([]float64, 0) - imps2 := make([]openrtb2.Imp, 0) - - for i := range prices { - if prices[i] > 0 { - prices2 = append(prices2, prices[i]) - imps2 = append(imps2, imps[i]) - } - } - - return prices2, imps2 -} - -// Helpers to test equality - -func assertEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected != actual { - msg = fmt.Sprintf("%s: act(%v) != exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertNotEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected == actual { - msg = fmt.Sprintf("%s: act(%v) == exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertTrue(t *testing.T, val bool, msg string) { - if val == false { - msg = fmt.Sprintf("%s: is false but should be true", msg) - t.Fatal(msg) - } -} - -func assertFalse(t *testing.T, val bool, msg string) { - if val == true { - msg = fmt.Sprintf("%s: is true but should be false", msg) - t.Fatal(msg) - } -} diff --git a/adapters/deepintent/deepintent.go b/adapters/deepintent/deepintent.go index b5b0fd54c5d..0853bb8b405 100644 --- a/adapters/deepintent/deepintent.go +++ b/adapters/deepintent/deepintent.go @@ -20,10 +20,6 @@ type DeepintentAdapter struct { URI string } -type deepintentParams struct { - tagId string `json:"tagId"` -} - // Builder builds a new instance of the Deepintent adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &DeepintentAdapter{ diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go index 409290c110d..aa4a6f79053 100644 --- a/adapters/dmx/dmx_test.go +++ b/adapters/dmx/dmx_test.go @@ -13,10 +13,6 @@ import ( "github.com/prebid/prebid-server/adapters/adapterstest" ) -var ( - bidRequest string -) - func TestFetchParams(t *testing.T) { var w, h int = 300, 250 diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go index 375248137ad..62bcc08d7cb 100644 --- a/adapters/infoawarebidder_test.go +++ b/adapters/infoawarebidder_test.go @@ -178,7 +178,6 @@ func TestImpFiltering(t *testing.T) { } type mockBidder struct { - gotRequest *openrtb2.BidRequest } func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index c79eda31040..1cfec69322d 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -1,259 +1,27 @@ package ix import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "sort" "strings" - "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" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/mxmCherry/openrtb/v15/native1" + native1response "github.com/mxmCherry/openrtb/v15/native1/response" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type IxAdapter struct { - http *adapters.HTTPAdapter URI string maxRequests int } -func (a *IxAdapter) Name() string { - return string(openrtb_ext.BidderIx) -} - -func (a *IxAdapter) SkipNoCookies() bool { - return false -} - -type indexParams struct { - SiteID string `json:"siteId"` -} - -type ixBidResult struct { - Request *callOneObject - StatusCode int - ResponseBody string - Bid *pbs.PBSBid - Error error -} - -type callOneObject struct { - requestJSON bytes.Buffer - width int64 - height int64 - bidType string -} - -func (a *IxAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - var prioritizedRequests, requests []callOneObject - - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - indexReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - if err != nil { - return nil, err - } - - indexReqImp := indexReq.Imp - for i, unit := range bidder.AdUnits { - // Supposedly fixes some segfaults - if len(indexReqImp) <= i { - break - } - - var params indexParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("unmarshal params '%s' failed: %v", unit.Params, err), - } - } - - if params.SiteID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing siteId param", - } - } - - for sizeIndex, format := range unit.Sizes { - // Only grab this ad unit. Not supporting multi-media-type adunit yet. - thisImp := indexReqImp[i] - - thisImp.TagID = unit.Code - if thisImp.Banner != nil { - thisImp.Banner.Format = []openrtb2.Format{format} - thisImp.Banner.W = &format.W - thisImp.Banner.H = &format.H - } - indexReq.Imp = []openrtb2.Imp{thisImp} - // Index spec says "adunit path representing ad server inventory" but we don't have this - // ext is DFP div ID and KV pairs if avail - //indexReq.Imp[i].Ext = json.RawMessage("{}") - - if indexReq.Site != nil { - // Any objects pointed to by indexReq *must not be mutated*, or we will get race conditions. - siteCopy := *indexReq.Site - siteCopy.Publisher = &openrtb2.Publisher{ID: params.SiteID} - indexReq.Site = &siteCopy - } - - bidType := "" - if thisImp.Banner != nil { - bidType = string(openrtb_ext.BidTypeBanner) - } else if thisImp.Video != nil { - bidType = string(openrtb_ext.BidTypeVideo) - } - j, _ := json.Marshal(indexReq) - request := callOneObject{requestJSON: *bytes.NewBuffer(j), width: format.W, height: format.H, bidType: bidType} - - // prioritize slots over sizes - if sizeIndex == 0 { - prioritizedRequests = append(prioritizedRequests, request) - } else { - requests = append(requests, request) - } - } - } - - // cap the number of requests to maxRequests - requests = append(prioritizedRequests, requests...) - if len(requests) > a.maxRequests { - requests = requests[:a.maxRequests] - } - - if len(requests) == 0 { - return nil, &errortypes.BadInput{ - Message: "Invalid ad unit/imp/size", - } - } - - ch := make(chan ixBidResult) - for _, request := range requests { - go func(bidder *pbs.PBSBidder, request callOneObject) { - result, err := a.callOne(ctx, request.requestJSON) - result.Request = &request - result.Error = err - if result.Bid != nil { - result.Bid.BidderCode = bidder.BidderCode - result.Bid.BidID = bidder.LookupBidID(result.Bid.AdUnitCode) - result.Bid.Width = request.width - result.Bid.Height = request.height - result.Bid.CreativeMediaType = request.bidType - - 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, request) - } - - bids := make(pbs.PBSBidSlice, 0) - for i := 0; i < len(requests); i++ { - result := <-ch - if result.Bid != nil && result.Bid.Price != 0 { - bids = append(bids, result.Bid) - } - - if req.IsDebug { - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - RequestBody: result.Request.requestJSON.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 (a *IxAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (ixBidResult, error) { - var result ixBidResult - - httpReq, _ := http.NewRequest("POST", a.URI, &reqJSON) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - ixResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return result, err - } - - result.StatusCode = ixResp.StatusCode - - if ixResp.StatusCode == http.StatusNoContent { - return result, nil - } - - if ixResp.StatusCode == http.StatusBadRequest { - return result, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), - } - } - - if ixResp.StatusCode != http.StatusOK { - return result, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), - } - } - - defer ixResp.Body.Close() - body, err := ioutil.ReadAll(ixResp.Body) - if err != nil { - return result, err - } - result.ResponseBody = string(body) - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return result, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Error parsing response: %v", err), - } - } - - if len(bidResp.SeatBid) == 0 { - return result, nil - } - if len(bidResp.SeatBid[0].Bid) == 0 { - return result, nil - } - bid := bidResp.SeatBid[0].Bid[0] - - pbid := pbs.PBSBid{ - AdUnitCode: bid.ImpID, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - - result.Bid = &pbid - return result, nil -} - func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { nImp := len(request.Imp) if nImp > a.maxRequests { @@ -454,14 +222,6 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque return bidderResponse, errs } -func NewIxLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *IxAdapter { - return &IxAdapter{ - http: adapters.NewHTTPAdapter(config), - URI: endpoint, - maxRequests: 20, - } -} - // Builder builds a new instance of the Ix adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &IxAdapter{ diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index d292273a92c..fc1d0f9a0a2 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -1,22 +1,15 @@ package ix import ( - "context" "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/http/httptest" "testing" - "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const endpoint string = "http://host/endpoint" @@ -31,698 +24,6 @@ func TestJsonSamples(t *testing.T) { } } -// Tests for the legacy, non-openrtb code. -// They can be removed after the legacy interface is deprecated. - -func getAdUnit() pbs.PBSAdUnit { - return pbs.PBSAdUnit{ - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{\"siteId\":\"12\"}"), - } -} - -func getVideoAdUnit() pbs.PBSAdUnit { - return pbs.PBSAdUnit{ - Code: "unitCodeVideo", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - BidID: "bididvideo", - Sizes: []openrtb2.Format{ - { - W: 100, - H: 75, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{2, 3}, - }, - Params: json.RawMessage("{\"siteId\":\"12\"}"), - } -} - -func getOpenRTBBid(i openrtb2.Imp) openrtb2.Bid { - return openrtb2.Bid{ - ID: fmt.Sprintf("%d", rand.Intn(1000)), - ImpID: i.ID, - Price: 1.0, - AdM: "Content", - } -} - -func newAdapter(endpoint string) *IxAdapter { - return NewIxLegacyAdapter(adapters.DefaultHTTPAdapterConfig, endpoint) -} - -func dummyIXServer(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 - } - - impression := breq.Imp[0] - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - getOpenRTBBid(impression), - }, - }, - }, - } - - 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 TestIxSkipNoCookies(t *testing.T) { - if newAdapter(endpoint).SkipNoCookies() { - t.Fatalf("SkipNoCookies must return false") - } -} - -func TestIxInvalidCall(t *testing.T) { - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxInvalidCallReqAppNil(t *testing.T) { - ctx := context.TODO() - pbReq := pbs.PBSRequest{ - App: &openrtb2.App{}, - } - pbBidder := pbs.PBSBidder{} - - _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxInvalidCallMissingSiteID(t *testing.T) { - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Params = json.RawMessage("{}") - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - _, err := newAdapter(endpoint).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for request with missing siteId") - } -} - -func TestIxTimeout(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil || err != context.DeadlineExceeded { - t.Fatalf("Invalid timeout error received") - } -} - -func TestIxTimeoutMultipleSlots(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - impression := breq.Imp[0] - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - getOpenRTBBid(impression), - }, - }, - }, - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // cancel the request before 2nd impression is returned - // delay to let 1st impression return successfully - if impression.ID == "unitCode2" { - <-time.After(10 * time.Millisecond) - cancel() - <-r.Context().Done() - } - - w.Header().Set("Content-Type", "application/json") - w.Write(js) - }), - ) - defer server.Close() - - pbReq := pbs.PBSRequest{} - - adUnit1 := getAdUnit() - adUnit2 := getAdUnit() - adUnit2.Code = "unitCode2" - adUnit2.Sizes = []openrtb2.Format{ - { - W: 8, - H: 10, - }, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit1, - adUnit2, - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } - - bid := findBidByAdUnitCode(bids, adUnit1.Code) - if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { - t.Fatalf("Received the wrong size") - } -} - -func TestIxInvalidJsonResponse(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Blah") - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxInvalidStatusCode(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 404 - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{IsDebug: true} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestIxBadRequest(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 400 - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for bad request") - } -} - -func TestIxNoContent(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 204 - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil || bids != nil { - t.Fatalf("Must return nil for no content") - } -} - -func TestIxInvalidCallMissingSize(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Sizes = []openrtb2.Format{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should not have gotten an error for missing/invalid size: %v", err) - } -} - -func TestIxInvalidCallEmptyBidIDResponse(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.BidID = "" - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should have gotten an error for unknown adunit code") - } -} - -func TestIxMismatchUnitCode(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: []openrtb2.Bid{ - { - ID: fmt.Sprintf("%d", rand.Intn(1000)), - ImpID: "unitCode_bogus", - Price: 1.0, - AdM: "Content", - W: 10, - H: 12, - }, - }, - }, - }, - } - - 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) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should have gotten an error for unknown adunit code") - } -} - -func TestNoSeatBid(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{} - - 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) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } -} - -func TestNoSeatBidBid(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - {}, - }, - } - - 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) - }), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } -} - -func TestIxInvalidParam(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Params = json.RawMessage("Bogus invalid input") - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - if _, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder); err == nil { - t.Fatalf("Should have gotten an error for unrecognized params") - } -} - -func TestIxSingleSlotSingleValidSize(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - getAdUnit(), - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} - -func TestIxTwoSlotValidSize(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit1 := getAdUnit() - adUnit2 := getVideoAdUnit() - adUnit2.Params = json.RawMessage("{\"siteId\":\"1111\"}") - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit1, - adUnit2, - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 2 { - t.Fatalf("Should have received two bid") - } - - bid := findBidByAdUnitCode(bids, adUnit1.Code) - if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { - t.Fatalf("Received the wrong size") - } - - bid = findBidByAdUnitCode(bids, adUnit2.Code) - if adUnit2.Sizes[0].H != bid.Height || adUnit2.Sizes[0].W != bid.Width { - t.Fatalf("Received the wrong size") - } -} - -func TestIxTwoSlotMultiSizeOnlyValidIXSizeResponse(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnit := getAdUnit() - adUnit.Sizes = append(adUnit.Sizes, openrtb2.Format{W: 20, H: 22}) - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - adUnit, - }, - } - bids, err := newAdapter(server.URL).Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != 2 { - t.Fatalf("Should have received 2 bids") - } - - for _, size := range adUnit.Sizes { - if !bidResponseForSizeExist(bids, size.H, size.W) { - t.Fatalf("Missing bid for specified size %d and %d", size.W, size.H) - } - } -} - -func bidResponseForSizeExist(bids pbs.PBSBidSlice, h, w int64) bool { - for _, v := range bids { - if v.Height == h && v.Width == w { - return true - } - } - return false -} - -func findBidByAdUnitCode(bids pbs.PBSBidSlice, c string) *pbs.PBSBid { - for _, v := range bids { - if v.AdUnitCode == c { - return v - } - } - return &pbs.PBSBid{} -} - -func TestIxMaxRequests(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(dummyIXServer), - ) - defer server.Close() - - adapter := newAdapter(server.URL) - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - adUnits := []pbs.PBSAdUnit{} - - for i := 0; i < adapter.maxRequests+1; i++ { - adUnits = append(adUnits, getAdUnit()) - } - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: adUnits, - } - - bids, err := adapter.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - - if len(bids) != adapter.maxRequests { - t.Fatalf("Should have received %d bid", adapter.maxRequests) - } -} - func TestIxMakeBidsWithCategoryDuration(t *testing.T) { bidder := &IxAdapter{} diff --git a/adapters/legacy.go b/adapters/legacy.go deleted file mode 100644 index 8b2221fe0ca..00000000000 --- a/adapters/legacy.go +++ /dev/null @@ -1,97 +0,0 @@ -package adapters - -import ( - "context" - "crypto/tls" - "net/http" - "time" - - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/server/ssl" -) - -// This file contains some deprecated, legacy types. -// -// These support the `/auction` endpoint, but will be replaced by `/openrtb2/auction`. -// New demand partners should ignore this file, and implement the Bidder interface. - -// Adapter is a deprecated interface which connects prebid-server to a demand partner. -// PBS is currently being rewritten to use Bidder, and this will be removed after. -// Their primary purpose is to produce bids in response to Auction requests. -type Adapter interface { - // Name must be identical to the BidderName. - Name() string - // Determines whether this adapter should get callouts if there is not a synched user ID. - SkipNoCookies() bool - // Call produces bids which should be considered, given the auction params. - // - // In practice, implementations almost always make one call to an external server here. - // However, that is not a requirement for satisfying this interface. - // - // An error here will cause all bids to be ignored. If the error was caused by bad user input, - // this should return a BadInputError. If it was caused by bad server behavior - // (e.g. 500, unexpected response format, etc), this should return a BadServerResponseError. - Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) -} - -// HTTPAdapterConfig groups options which control how HTTP requests are made by adapters. -type HTTPAdapterConfig struct { - // See IdleConnTimeout on https://golang.org/pkg/net/http/#Transport - IdleConnTimeout time.Duration - // See MaxIdleConns on https://golang.org/pkg/net/http/#Transport - MaxConns int - // See MaxIdleConnsPerHost on https://golang.org/pkg/net/http/#Transport - MaxConnsPerHost int -} - -type HTTPAdapter struct { - Client *http.Client -} - -// DefaultHTTPAdapterConfig is an HTTPAdapterConfig that chooses sensible default values. -var DefaultHTTPAdapterConfig = &HTTPAdapterConfig{ - MaxConns: 50, - MaxConnsPerHost: 10, - IdleConnTimeout: 60 * time.Second, -} - -// NewHTTPAdapter creates an HTTPAdapter which obeys the rules given by the config, and -// has all the available SSL certs available in the project. -func NewHTTPAdapter(c *HTTPAdapterConfig) *HTTPAdapter { - ts := &http.Transport{ - MaxIdleConns: c.MaxConns, - MaxIdleConnsPerHost: c.MaxConnsPerHost, - IdleConnTimeout: c.IdleConnTimeout, - TLSClientConfig: &tls.Config{RootCAs: ssl.GetRootCAPool()}, - } - - return &HTTPAdapter{ - Client: &http.Client{ - Transport: ts, - }, - } -} - -// used for callOne (possibly pull all of the shared code here) -type CallOneResult struct { - StatusCode int - ResponseBody string - Bid *pbs.PBSBid - Error error -} - -type MisconfiguredAdapter struct { - TheName string - Err error -} - -func (b *MisconfiguredAdapter) Name() string { - return b.TheName -} -func (b *MisconfiguredAdapter) SkipNoCookies() bool { - return false -} - -func (b *MisconfiguredAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - return nil, b.Err -} diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go deleted file mode 100644 index 6aa07c6b764..00000000000 --- a/adapters/openrtb_util.go +++ /dev/null @@ -1,174 +0,0 @@ -package adapters - -import ( - "encoding/json" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/pbs" -) - -func min(x, y int) int { - if x < y { - return x - } - return y -} - -func mediaTypeInSlice(t pbs.MediaType, list []pbs.MediaType) bool { - for _, b := range list { - if b == t { - return true - } - } - return false -} - -func commonMediaTypes(l1 []pbs.MediaType, l2 []pbs.MediaType) []pbs.MediaType { - res := make([]pbs.MediaType, min(len(l1), len(l2))) - i := 0 - for _, b := range l1 { - if mediaTypeInSlice(b, l2) { - res[i] = b - i = i + 1 - } - } - return res[:i] -} - -func makeBanner(unit pbs.PBSAdUnit) *openrtb2.Banner { - return &openrtb2.Banner{ - W: openrtb2.Int64Ptr(unit.Sizes[0].W), - H: openrtb2.Int64Ptr(unit.Sizes[0].H), - Format: copyFormats(unit.Sizes), // defensive copy because adapters may mutate Imps, and this is shared data - TopFrame: unit.TopFrame, - } -} - -func makeVideo(unit pbs.PBSAdUnit) *openrtb2.Video { - // empty mimes array is a sign of uninitialized Video object - if len(unit.Video.Mimes) < 1 { - return nil - } - mimes := make([]string, len(unit.Video.Mimes)) - copy(mimes, unit.Video.Mimes) - pbm := make([]openrtb2.PlaybackMethod, 1) - //this will become int8 soon, so we only care about the first index in the array - pbm[0] = openrtb2.PlaybackMethod(unit.Video.PlaybackMethod) - - protocols := make([]openrtb2.Protocol, 0, len(unit.Video.Protocols)) - for _, protocol := range unit.Video.Protocols { - protocols = append(protocols, openrtb2.Protocol(protocol)) - } - return &openrtb2.Video{ - MIMEs: mimes, - MinDuration: unit.Video.Minduration, - MaxDuration: unit.Video.Maxduration, - W: unit.Sizes[0].W, - H: unit.Sizes[0].H, - StartDelay: openrtb2.StartDelay(unit.Video.Startdelay).Ptr(), - PlaybackMethod: pbm, - Protocols: protocols, - } -} - -// adapters.MakeOpenRTBGeneric makes an openRTB request from the PBS-specific structs. -// -// Any objects pointed to by the returned BidRequest *must not be mutated*, or we will get race conditions. -// The only exception is the Imp property, whose objects will be created new by this method and can be mutated freely. -func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily string, allowedMediatypes []pbs.MediaType) (openrtb2.BidRequest, error) { - imps := make([]openrtb2.Imp, 0, len(bidder.AdUnits)*len(allowedMediatypes)) - for _, unit := range bidder.AdUnits { - if len(unit.Sizes) <= 0 { - continue - } - unitMediaTypes := commonMediaTypes(unit.MediaTypes, allowedMediatypes) - if len(unitMediaTypes) == 0 { - continue - } - - newImp := openrtb2.Imp{ - ID: unit.Code, - Secure: &req.Secure, - Instl: unit.Instl, - } - for _, mType := range unitMediaTypes { - switch mType { - case pbs.MEDIA_TYPE_BANNER: - newImp.Banner = makeBanner(unit) - case pbs.MEDIA_TYPE_VIDEO: - newImp.Video = makeVideo(unit) - // It's strange to error here... but preserves legacy behavior in legacy code. See #603. - if newImp.Video == nil { - return openrtb2.BidRequest{}, &errortypes.BadInput{ - Message: "Invalid AdUnit: VIDEO media type with no video data", - } - } - } - } - if newImp.Banner != nil || newImp.Video != nil { - imps = append(imps, newImp) - } - } - - if len(imps) < 1 { - return openrtb2.BidRequest{}, &errortypes.BadInput{ - Message: "openRTB bids need at least one Imp", - } - } - - if req.App != nil { - return openrtb2.BidRequest{ - ID: req.Tid, - Imp: imps, - App: req.App, - Device: req.Device, - User: req.User, - Source: &openrtb2.Source{ - TID: req.Tid, - }, - AT: 1, - TMax: req.TimeoutMillis, - Regs: req.Regs, - }, nil - } - - buyerUID, _, _ := req.Cookie.GetUID(bidderFamily) - id, _, _ := req.Cookie.GetUID("adnxs") - - var userExt json.RawMessage - if req.User != nil { - userExt = req.User.Ext - } - - return openrtb2.BidRequest{ - ID: req.Tid, - Imp: imps, - Site: &openrtb2.Site{ - Domain: req.Domain, - Page: req.Url, - }, - Device: req.Device, - User: &openrtb2.User{ - BuyerUID: buyerUID, - ID: id, - Ext: userExt, - }, - Source: &openrtb2.Source{ - FD: 1, // upstream, aka header - TID: req.Tid, - }, - AT: 1, - TMax: req.TimeoutMillis, - Regs: req.Regs, - }, nil -} - -func copyFormats(sizes []openrtb2.Format) []openrtb2.Format { - sizesCopy := make([]openrtb2.Format, len(sizes)) - for i := 0; i < len(sizes); i++ { - sizesCopy[i] = sizes[i] - sizesCopy[i].Ext = append([]byte(nil), sizes[i].Ext...) - } - return sizesCopy -} diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go deleted file mode 100644 index 035f4d9b679..00000000000 --- a/adapters/openrtb_util_test.go +++ /dev/null @@ -1,543 +0,0 @@ -package adapters - -import ( - "testing" - - "encoding/json" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - "github.com/stretchr/testify/assert" -) - -func TestCommonMediaTypes(t *testing.T) { - mt1 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - mt2 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - common := commonMediaTypes(mt1, mt2) - assert.Equal(t, len(common), 1) - assert.Equal(t, common[0], pbs.MEDIA_TYPE_BANNER) - - common2 := commonMediaTypes(mt2, mt1) - assert.Equal(t, len(common2), 1) - assert.Equal(t, common2[0], pbs.MEDIA_TYPE_BANNER) - - mt3 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - mt4 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - common3 := commonMediaTypes(mt3, mt4) - assert.Equal(t, len(common3), 2) - - mt5 := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - mt6 := []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO} - common4 := commonMediaTypes(mt5, mt6) - assert.Equal(t, len(common4), 0) -} - -func TestOpenRTB(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Instl: 1, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 12) - assert.EqualValues(t, resp.Imp[0].Instl, 1) - - assert.Nil(t, resp.User.Ext) - assert.Nil(t, resp.Regs) -} - -func TestOpenRTBVideo(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}) - - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) - assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) - assert.EqualValues(t, *resp.Imp[0].Video.StartDelay, openrtb2.StartDelay(5)) - assert.EqualValues(t, resp.Imp[0].Video.PlaybackMethod, []openrtb2.PlaybackMethod{openrtb2.PlaybackMethod(1)}) - assert.EqualValues(t, resp.Imp[0].Video.MIMEs, []string{"video/mp4"}) -} - -func TestOpenRTBVideoNoVideoData(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - }, - }, - } - _, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}) - - assert.NotEqual(t, err, nil) - -} - -func TestOpenRTBVideoFilteredOut(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - { - Code: "unitCode2", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - for i := 0; i < len(resp.Imp); i++ { - if resp.Imp[i].Video != nil { - t.Errorf("No video impressions should exist.") - } - } -} - -func TestOpenRTBMultiMediaImp(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, len(resp.Imp), 1) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, resp.Imp[0].Video.W, 10) - assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) - assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) -} - -func TestOpenRTBMultiMediaImpFiltered(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, len(resp.Imp), 1) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, resp.Imp[0].Video, (*openrtb2.Video)(nil)) -} - -func TestOpenRTBNoSize(t *testing.T) { - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - }, - }, - } - _, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - if err == nil { - t.Errorf("Bids without impressions should not be allowed.") - } -} - -func TestOpenRTBMobile(t *testing.T) { - pbReq := pbs.PBSRequest{ - AccountID: "test_account_id", - Tid: "test_tid", - CacheMarkup: 1, - SortBids: 1, - MaxKeyLength: 20, - Secure: 1, - TimeoutMillis: 1000, - App: &openrtb2.App{ - Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb2.Publisher{ - ID: "1995257847363113", - }, - }, - Device: &openrtb2.Device{ - UA: "test_ua", - IP: "test_ip", - Make: "test_make", - Model: "test_model", - IFA: "test_ifa", - }, - User: &openrtb2.User{ - BuyerUID: "test_buyeruid", - }, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 300) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 250) - - assert.EqualValues(t, resp.App.Bundle, "AppNexus.PrebidMobileDemo") - assert.EqualValues(t, resp.App.Publisher.ID, "1995257847363113") - assert.EqualValues(t, resp.User.BuyerUID, "test_buyeruid") - - assert.EqualValues(t, resp.Device.UA, "test_ua") - assert.EqualValues(t, resp.Device.IP, "test_ip") - assert.EqualValues(t, resp.Device.Make, "test_make") - assert.EqualValues(t, resp.Device.Model, "test_model") - assert.EqualValues(t, resp.Device.IFA, "test_ifa") -} - -func TestOpenRTBEmptyUser(t *testing.T) { - pbReq := pbs.PBSRequest{ - User: &openrtb2.User{}, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode2", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.EqualValues(t, resp.User, &openrtb2.User{}) -} - -func TestOpenRTBUserWithCookie(t *testing.T) { - pbsCookie := usersync.NewCookie() - pbsCookie.TrySync("test", "abcde") - pbReq := pbs.PBSRequest{ - User: &openrtb2.User{}, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - }, - }, - } - pbReq.Cookie = pbsCookie - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.EqualValues(t, resp.User.BuyerUID, "abcde") -} - -func TestSizesCopy(t *testing.T) { - formats := []openrtb2.Format{ - { - W: 10, - }, - { - Ext: []byte{0x5}, - }, - } - clone := copyFormats(formats) - - if len(clone) != 2 { - t.Error("The copy should have 2 elements") - } - if clone[0].W != 10 { - t.Error("The Format's width should be preserved.") - } - if len(clone[1].Ext) != 1 || clone[1].Ext[0] != 0x5 { - t.Error("The Format's Ext should be preserved.") - } - if &formats[0] == &clone[0] || &formats[1] == &clone[1] { - t.Error("The Format elements should not point to the same instance") - } - if &formats[0] == &clone[0] || &formats[1] == &clone[1] { - t.Error("The Format elements should not point to the same instance") - } - if &formats[1].Ext[0] == &clone[1].Ext[0] { - t.Error("The Format.Ext property should point to two different instances") - } -} - -func TestMakeVideo(t *testing.T) { - adUnit := pbs.PBSAdUnit{ - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Video: pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{1, 2, 5, 6}, - }, - } - video := makeVideo(adUnit) - assert.EqualValues(t, video.MinDuration, 15) - assert.EqualValues(t, video.MaxDuration, 30) - assert.EqualValues(t, *video.StartDelay, openrtb2.StartDelay(5)) - assert.EqualValues(t, len(video.PlaybackMethod), 1) - assert.EqualValues(t, len(video.Protocols), 4) -} - -func TestGDPR(t *testing.T) { - - rawUserExt := json.RawMessage(`{"consent": "12345"}`) - userExt, _ := json.Marshal(rawUserExt) - - rawRegsExt := json.RawMessage(`{"gdpr": 1}`) - regsExt, _ := json.Marshal(rawRegsExt) - - pbReq := pbs.PBSRequest{ - User: &openrtb2.User{ - Ext: userExt, - }, - Regs: &openrtb2.Regs{ - Ext: regsExt, - }, - } - - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Instl: 1, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 12) - assert.EqualValues(t, resp.Imp[0].Instl, 1) - - assert.EqualValues(t, resp.User.Ext, userExt) - assert.EqualValues(t, resp.Regs.Ext, regsExt) -} - -func TestGDPRMobile(t *testing.T) { - rawUserExt := json.RawMessage(`{"consent": "12345"}`) - userExt, _ := json.Marshal(rawUserExt) - - rawRegsExt := json.RawMessage(`{"gdpr": 1}`) - regsExt, _ := json.Marshal(rawRegsExt) - - pbReq := pbs.PBSRequest{ - AccountID: "test_account_id", - Tid: "test_tid", - CacheMarkup: 1, - SortBids: 1, - MaxKeyLength: 20, - Secure: 1, - TimeoutMillis: 1000, - App: &openrtb2.App{ - Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb2.Publisher{ - ID: "1995257847363113", - }, - }, - Device: &openrtb2.Device{ - UA: "test_ua", - IP: "test_ip", - Make: "test_make", - Model: "test_model", - IFA: "test_ifa", - }, - User: &openrtb2.User{ - BuyerUID: "test_buyeruid", - Ext: userExt, - }, - Regs: &openrtb2.Regs{ - Ext: regsExt, - }, - } - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 300, - H: 250, - }, - }, - }, - }, - } - resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) - assert.Equal(t, err, nil) - assert.Equal(t, resp.Imp[0].ID, "unitCode") - assert.EqualValues(t, *resp.Imp[0].Banner.W, 300) - assert.EqualValues(t, *resp.Imp[0].Banner.H, 250) - - assert.EqualValues(t, resp.App.Bundle, "AppNexus.PrebidMobileDemo") - assert.EqualValues(t, resp.App.Publisher.ID, "1995257847363113") - assert.EqualValues(t, resp.User.BuyerUID, "test_buyeruid") - - assert.EqualValues(t, resp.Device.UA, "test_ua") - assert.EqualValues(t, resp.Device.IP, "test_ip") - assert.EqualValues(t, resp.Device.Make, "test_make") - assert.EqualValues(t, resp.Device.Model, "test_model") - assert.EqualValues(t, resp.Device.IFA, "test_ifa") - - assert.EqualValues(t, resp.User.Ext, userExt) - assert.EqualValues(t, resp.Regs.Ext, regsExt) -} diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index c2e9fffa0fe..19024f4a123 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -1,11 +1,8 @@ package pubmatic import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "strconv" "strings" @@ -16,46 +13,23 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" ) const MAX_IMPRESSIONS_PUBMATIC = 30 type PubmaticAdapter struct { - http *adapters.HTTPAdapter - URI string + URI string } -// used for cookies and such -func (a *PubmaticAdapter) Name() string { - return "pubmatic" -} - -func (a *PubmaticAdapter) SkipNoCookies() bool { - return false -} - -// Below is bidder specific parameters for pubmatic adaptor, -// PublisherId and adSlot are mandatory parameters, others are optional parameters -// Keywords is bid specific parameter, -// WrapExt needs to be sent once per bid request -type pubmaticParams struct { - PublisherId string `json:"publisherId"` - AdSlot string `json:"adSlot"` - WrapExt json.RawMessage `json:"wrapper,omitempty"` - Keywords map[string]string `json:"keywords,omitempty"` +type pubmaticBidExt struct { + BidType *int `json:"BidType,omitempty"` + VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` } type pubmaticBidExtVideo struct { Duration *int `json:"duration,omitempty"` } -type pubmaticBidExt struct { - BidType *int `json:"BidType,omitempty"` - VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` -} - type ExtImpBidderPubmatic struct { adapters.ExtImpBidder Data *ExtData `json:"data,omitempty"` @@ -72,16 +46,6 @@ type ExtAdServer struct { } const ( - INVALID_PARAMS = "Invalid BidParam" - MISSING_PUBID = "Missing PubID" - MISSING_ADSLOT = "Missing AdSlot" - INVALID_WRAPEXT = "Invalid WrapperExt" - INVALID_ADSIZE = "Invalid AdSize" - INVALID_WIDTH = "Invalid Width" - INVALID_HEIGHT = "Invalid Height" - INVALID_MEDIATYPE = "Invalid MediaType" - INVALID_ADSLOT = "Invalid AdSlot" - dctrKeyName = "key_val" pmZoneIDKeyName = "pmZoneId" pmZoneIDKeyNameOld = "pmZoneID" @@ -89,251 +53,6 @@ const ( AdServerGAM = "gam" ) -func PrepareLogMessage(tID, pubId, adUnitId, bidID, details string, args ...interface{}) string { - return fmt.Sprintf("[PUBMATIC] ReqID [%s] PubID [%s] AdUnit [%s] BidID [%s] %s \n", - tID, pubId, adUnitId, bidID, details) -} - -func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - pbReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - logf("[PUBMATIC] Failed to make ortb request for request id [%s] \n", pbReq.ID) - return nil, err - } - - var errState []string - adSlotFlag := false - pubId := "" - wrapExt := "" - if len(bidder.AdUnits) > MAX_IMPRESSIONS_PUBMATIC { - logf("[PUBMATIC] First %d impressions will be considered from request tid %s\n", - MAX_IMPRESSIONS_PUBMATIC, pbReq.ID) - } - - for i, unit := range bidder.AdUnits { - var params pubmaticParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_PARAMS, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid JSON [%s] err [%s]", unit.Params, err.Error()))) - continue - } - - if params.PublisherId == "" { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, MISSING_PUBID, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: Publisher Id missing"))) - continue - } - pubId = params.PublisherId - - if params.AdSlot == "" { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, MISSING_ADSLOT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: adSlot missing"))) - continue - } - - // Parse Wrapper Extension i.e. ProfileID and VersionID only once per request - if wrapExt == "" && len(params.WrapExt) != 0 { - var wrapExtMap map[string]int - err := json.Unmarshal([]byte(params.WrapExt), &wrapExtMap) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_WRAPEXT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: Wrapper Extension Invalid"))) - continue - } - wrapExt = string(params.WrapExt) - } - - adSlotStr := strings.TrimSpace(params.AdSlot) - adSlot := strings.Split(adSlotStr, "@") - if len(adSlot) == 2 && adSlot[0] != "" && adSlot[1] != "" { - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(pbReq.Imp) <= i { - break - } - if pbReq.Imp[i].Banner != nil { - adSize := strings.Split(strings.ToLower(strings.TrimSpace(adSlot[1])), "x") - if len(adSize) == 2 { - width, err := strconv.Atoi(strings.TrimSpace(adSize[0])) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_WIDTH, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSlot width [%s]", adSize[0]))) - continue - } - - heightStr := strings.Split(strings.TrimSpace(adSize[1]), ":") - height, err := strconv.Atoi(strings.TrimSpace(heightStr[0])) - if err != nil { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_HEIGHT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSlot height [%s]", heightStr[0]))) - continue - } - - pbReq.Imp[i].TagID = strings.TrimSpace(adSlot[0]) - 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) - pbReq.Imp[i].Ext = json.RawMessage([]byte(kvstr)) - } else { - pbReq.Imp[i].Ext = nil - } - - adSlotFlag = true - } else { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_ADSIZE, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSize [%s]", adSize))) - continue - } - } else { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_MEDIATYPE, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid Media Type"))) - continue - } - } else { - errState = append(errState, fmt.Sprintf("BidID:%s;Error:%s;param:%s", unit.BidID, INVALID_ADSLOT, unit.Params)) - logf(PrepareLogMessage(pbReq.ID, params.PublisherId, unit.Code, unit.BidID, - fmt.Sprintf("Ignored bid: invalid adSlot [%s]", params.AdSlot))) - continue - } - - if pbReq.Site != nil { - siteCopy := *pbReq.Site - siteCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} - pbReq.Site = &siteCopy - } - if pbReq.App != nil { - appCopy := *pbReq.App - appCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} - pbReq.App = &appCopy - } - } - - if !(adSlotFlag) { - return nil, &errortypes.BadInput{ - Message: "Incorrect adSlot / Publisher params, Error list: [" + strings.Join(errState, ",") + "]", - } - } - - if wrapExt != "" { - rawExt := fmt.Sprintf("{\"wrapper\": %s}", wrapExt) - pbReq.Ext = json.RawMessage(rawExt) - } - - reqJSON, err := json.Marshal(pbReq) - - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - } - - userId, _, _ := req.Cookie.GetUID(a.Name()) - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON)) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - httpReq.AddCookie(&http.Cookie{ - Name: "KADUSERCOOKIE", - Value: userId, - }) - - pbResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - debug.StatusCode = pbResp.StatusCode - - if pbResp.StatusCode == http.StatusNoContent { - return nil, nil - } - - if pbResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), - } - } - - if pbResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), - } - } - - defer pbResp.Body.Close() - body, err := ioutil.ReadAll(pbResp.Body) - if err != nil { - return nil, err - } - - if req.IsDebug { - debug.ResponseBody = string(body) - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", pbResp.StatusCode), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - numBids := 0 - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - numBids++ - - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - - var bidExt pubmaticBidExt - mediaType := openrtb_ext.BidTypeBanner - if err := json.Unmarshal(bid.Ext, &bidExt); err == nil { - mediaType = getBidType(&bidExt) - } - pbid.CreativeMediaType = string(mediaType) - - bids = append(bids, &pbid) - logf("[PUBMATIC] Returned Bid for PubID [%s] AdUnit [%s] BidID [%s] Size [%dx%d] Price [%f] \n", - pubId, pbid.AdUnitCode, pbid.BidID, pbid.Width, pbid.Height, pbid.Price) - } - } - - return bids, nil -} - func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) @@ -535,7 +254,6 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er } return nil - } func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) { @@ -553,22 +271,6 @@ func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[s } } -func prepareImpressionExt(keywords map[string]string) string { - - eachKv := make([]string, 0, len(keywords)) - for key, val := range keywords { - if len(val) == 0 { - logf("No values present for key = %s", key) - continue - } else { - eachKv = append(eachKv, fmt.Sprintf("\"%s\":\"%s\"", key, val)) - } - } - - kvStr := "{" + strings.Join(eachKv, ",") + "}" - return kvStr -} - func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -646,15 +348,6 @@ func logf(msg string, args ...interface{}) { } } -func NewPubmaticLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PubmaticAdapter { - a := adapters.NewHTTPAdapter(config) - - return &PubmaticAdapter{ - http: a, - URI: uri, - } -} - // Builder builds a new instance of the Pubmatic adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &PubmaticAdapter{ diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 2e8a6804850..ac7dbdb711f 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -1,25 +1,11 @@ package pubmatic import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/http/httptest" "testing" - "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" ) func TestJsonSamples(t *testing.T) { @@ -33,655 +19,8 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "pubmatictest", bidder) } -// ---------------------------------------------------------------------------- -// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb2. - -func CompareStringValue(val1 string, val2 string, t *testing.T) { - if val1 != val2 { - t.Fatalf(fmt.Sprintf("Expected = %s , Actual = %s", val2, val1)) - } -} - -func DummyPubMaticServer(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 - } - - resp := openrtb2.BidResponse{ - ID: breq.ID, - BidID: "bidResponse_ID", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "pubmatic", - Bid: make([]openrtb2.Bid, 0), - }, - }, - } - rand.Seed(int64(time.Now().UnixNano())) - var bids []openrtb2.Bid - - for i, imp := range breq.Imp { - bids = append(bids, openrtb2.Bid{ - ID: fmt.Sprintf("SeatID_%d", i), - ImpID: imp.ID, - Price: float64(int(rand.Float64()*1000)) / 100, - AdID: fmt.Sprintf("adID-%d", i), - AdM: "AdContent", - CrID: fmt.Sprintf("creative-%d", i), - W: *imp.Banner.W, - H: *imp.Banner.H, - DealID: fmt.Sprintf("DealID_%d", i), - }) - } - resp.SeatBid[0].Bid = bids - - 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 TestPubmaticInvalidCall(t *testing.T) { - - an := NewPubmaticLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "blah") - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestPubmaticTimeout(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), - }, - }, - } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil || err != context.DeadlineExceeded { - t.Fatalf("No timeout received for timed out request: %v", err) - } -} - -func TestPubmaticInvalidJson(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "Blah") - }), - ) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), - }, - }, - } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestPubmaticInvalidStatusCode(t *testing.T) { - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send 404 - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}"), - }, - }, - } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("No error received for invalid request") - } -} - -func TestPubmaticInvalidInputParameters(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - }, - }, - } - - pbReq.IsDebug = true - inValidPubmaticParams := []json.RawMessage{ - // Invalid Request JSON - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\""), - // Missing adSlot in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\"}"), - // Missing publisher ID - json.RawMessage("{\"adSlot\": \"slot@120x240\"}"), - // Missing slot name in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"@120x240\"}"), - // Invalid adSize in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120-240\"}"), - // Missing impression width and height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@\"}"), - // Missing height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120\"}"), - // Missing width in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@x120\"}"), - // Incorrect width param in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@valx120\"}"), - // Incorrect height param in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120xval\"}"), - // Empty slot name in AdUnits.Params, - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" @120x240\"}"), - // Empty width in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@ x240\"}"), - // Empty height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x \"}"), - // Empty height in AdUnits.Params - json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" @120x \"}"), - // Invalid Keywords - json.RawMessage(`{"publisherId": "640", "adSlot": "slot1@336x280","keywords":{"pmZoneId":1},"wrapper":{"version":2,"profile":595}}`), - // Invalid Wrapper ext - json.RawMessage(`{"publisherId": "640", "adSlot": "slot1@336x280","keywords":{"pmZoneId":"Zone1,Zone2"},"wrapper":{"version":"2","profile":595}}`), - } - - for _, param := range inValidPubmaticParams { - pbBidder.AdUnits[0].Params = param - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { - t.Fatalf("Should get errors for params = %v", string(param)) - } - } - -} - -func TestPubmaticBasicResponse_MandatoryParams(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - }, - } - pbReq.IsDebug = true - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} - -func TestPubmaticBasicResponse_AllParams(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage(`{"publisherId": "640", - "adSlot": "slot1@336x280", - "keywords":{ - "pmZoneId": "Zone1,Zone2" - }, - "wrapper": - {"version":2, - "profile":595} - }`), - }, - }, - } - pbReq.IsDebug = true - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} - -func TestPubmaticMultiImpressionResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode1", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - { - Code: "unitCode1", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 800, - H: 200, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@800x200\"}"), - }, - }, - } - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 2 { - t.Fatalf("Should have received two bids") - } -} - -func TestPubmaticMultiAdUnitResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode1", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - { - Code: "unitCode2", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb2.Format{ - { - W: 800, - H: 200, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@800x200\"}"), - }, - }, - } - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 2 { - t.Fatalf("Should have received one bid") - } - -} - -func TestPubmaticMobileResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 336, - H: 280, - }, - }, - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@336x280\"}"), - }, - }, - } - - pbReq.App = &openrtb2.App{ - ID: "com.test", - Name: "testApp", - } - - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Should have received one bid") - } -} -func TestPubmaticInvalidLookupBidIDParameter(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - }, - }, - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240\"}") - _, err := an.Call(ctx, &pbReq, &pbBidder) - - CompareStringValue(err.Error(), "Unknown ad unit code 'unitCode'", t) -} - -func TestPubmaticAdSlotParams(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewPubmaticLegacyAdapter(&conf, server.URL) - - ctx := context.Background() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{ - BidderCode: "bannerCode", - AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - BidID: "bidid", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb2.Format{ - { - W: 120, - H: 240, - }, - }, - }, - }, - } - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \" slot@120x240\"}") - bids, err := an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot @120x240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240 \"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@ 120x240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@220 x240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x 240\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240:1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x 240:1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240 :1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } - - pbBidder.AdUnits[0].Params = json.RawMessage("{\"publisherId\": \"10\", \"adSlot\": \"slot@120x240: 1\"}") - bids, err = an.Call(ctx, &pbReq, &pbBidder) - if err != nil && len(bids) != 1 { - t.Fatalf("Should not return err") - } -} - -func TestPubmaticSampleRequest(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(DummyPubMaticServer)) - defer server.Close() - - pbReq := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 1), - } - pbReq.AdUnits[0] = pbs.AdUnit{ - Code: "adUnit_1", - Sizes: []openrtb2.Format{ - { - W: 100, - H: 120, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "pubmatic", - BidID: "BidID", - Params: json.RawMessage("{\"publisherId\": \"640\", \"adSlot\": \"slot1@100x120\"}"), - }, - }, - } - - pbReq.IsDebug = true - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbReq) - if err != nil { - t.Fatalf("Error when serializing request") - } - - httpReq := httptest.NewRequest("POST", server.URL, body) - httpReq.Header.Add("Referer", "http://test.com/sports") - pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) - pc.TrySync("pubmatic", "12345") - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - hcs := config.HostCookie{} - - _, err = pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcs) - if err != nil { - t.Fatalf("Error when parsing request: %v", err) - } -} - func TestGetBidTypeVideo(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 1 actualBidTypeValue := getBidType(pubmaticExt) @@ -691,8 +30,8 @@ func TestGetBidTypeVideo(t *testing.T) { } func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { - pubmaticExt := pubmaticBidExt{} - actualBidTypeValue := getBidType(&pubmaticExt) + pubmaticExt := &pubmaticBidExt{} + actualBidTypeValue := getBidType(pubmaticExt) // banner is the default bid type when no bidType key is present in the bid.ext if actualBidTypeValue != "banner" { t.Errorf("Expected Bid Type value was: banner, actual value is: %v", actualBidTypeValue) @@ -700,7 +39,7 @@ func TestGetBidTypeForMissingBidTypeExt(t *testing.T) { } func TestGetBidTypeBanner(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 0 actualBidTypeValue := getBidType(pubmaticExt) @@ -710,7 +49,7 @@ func TestGetBidTypeBanner(t *testing.T) { } func TestGetBidTypeNative(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 2 actualBidTypeValue := getBidType(pubmaticExt) @@ -720,7 +59,7 @@ func TestGetBidTypeNative(t *testing.T) { } func TestGetBidTypeForUnsupportedCode(t *testing.T) { - pubmaticExt := new(pubmaticBidExt) + pubmaticExt := &pubmaticBidExt{} pubmaticExt.BidType = new(int) *pubmaticExt.BidType = 99 actualBidTypeValue := getBidType(pubmaticExt) diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 6b6b4305607..f756d5dd31a 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -1,27 +1,21 @@ package pulsepoint import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "strconv" - "strings" - "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" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type PulsePointAdapter struct { - http *adapters.HTTPAdapter - URI string + URI string } // Builds an instance of PulsePointAdapter @@ -168,192 +162,3 @@ func getBidType(imp openrtb2.Imp) openrtb_ext.BidType { } return "" } - -///////////////////////////////// -// Legacy implementation: Start -///////////////////////////////// - -func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter { - a := adapters.NewHTTPAdapter(config) - - return &PulsePointAdapter{ - http: a, - URI: uri, - } -} - -// used for cookies and such -func (a *PulsePointAdapter) Name() string { - return "pulsepoint" -} - -// parameters for pulsepoint adapter. -type PulsepointParams struct { - PublisherId int `json:"cp"` - TagId int `json:"ct"` - AdSize string `json:"cf"` -} - -func (a *PulsePointAdapter) SkipNoCookies() bool { - return false -} - -func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - ppReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - return nil, err - } - - for i, unit := range bidder.AdUnits { - var params PulsepointParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - if params.PublisherId == 0 { - return nil, &errortypes.BadInput{ - Message: "Missing PublisherId param cp", - } - } - if params.TagId == 0 { - return nil, &errortypes.BadInput{ - Message: "Missing TagId param ct", - } - } - if params.AdSize == "" { - return nil, &errortypes.BadInput{ - Message: "Missing AdSize param cf", - } - } - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(ppReq.Imp) <= i { - break - } - ppReq.Imp[i].TagID = strconv.Itoa(params.TagId) - publisher := &openrtb2.Publisher{ID: strconv.Itoa(params.PublisherId)} - if ppReq.Site != nil { - siteCopy := *ppReq.Site - siteCopy.Publisher = publisher - ppReq.Site = &siteCopy - } else { - appCopy := *ppReq.App - appCopy.Publisher = publisher - ppReq.App = &appCopy - } - if ppReq.Imp[i].Banner != nil { - var size = strings.Split(strings.ToLower(params.AdSize), "x") - if len(size) == 2 { - width, err := strconv.Atoi(size[0]) - if err == nil { - ppReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) - } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid Width param %s", size[0]), - } - } - height, err := strconv.Atoi(size[1]) - if err == nil { - ppReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) - } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid Height param %s", size[1]), - } - } - } else { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid AdSize param %s", params.AdSize), - } - } - } - } - reqJSON, err := json.Marshal(ppReq) - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON)) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - ppResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - debug.StatusCode = ppResp.StatusCode - - if ppResp.StatusCode == http.StatusNoContent { - return nil, nil - } - - if ppResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d", ppResp.StatusCode), - } - } - - if ppResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d", ppResp.StatusCode), - } - } - - defer ppResp.Body.Close() - body, err := ioutil.ReadAll(ppResp.Body) - if err != nil { - return nil, err - } - - if req.IsDebug { - debug.ResponseBody = string(body) - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - CreativeMediaType: string(openrtb_ext.BidTypeBanner), - } - bids = append(bids, &pbid) - } - } - - return bids, nil -} - -///////////////////////////////// -// Legacy implementation: End -///////////////////////////////// diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index a4e20b04859..8929898522a 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -1,26 +1,11 @@ package pulsepoint import ( - "encoding/json" - "net/http" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/openrtb_ext" - - "bytes" - "context" - "fmt" - "io/ioutil" - "net/http/httptest" - "time" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestJsonSamples(t *testing.T) { @@ -33,280 +18,3 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "pulsepointtest", bidder) } - -///////////////////////////////// -// Legacy implementation: Start -///////////////////////////////// - -/** - * Verify adapter names are setup correctly. - */ -func TestPulsePointAdapterNames(t *testing.T) { - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://localhost/bid") - adapterstest.VerifyStringValue(adapter.Name(), "pulsepoint", t) -} - -/** - * Test required parameters not sent - */ -func TestPulsePointRequiredBidParameters(t *testing.T) { - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://localhost/bid") - ctx := context.TODO() - req := SampleRequest(1, t) - bidder := req.Bidders[0] - // remove "ct" param and verify error message. - bidder.AdUnits[0].Params = json.RawMessage("{\"cp\": 2001, \"cf\": \"728X90\"}") - _, errTag := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errTag.Error(), "Missing TagId param ct", t) - // remove "cp" param and verify error message. - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cf\": \"728X90\"}") - _, errPub := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errPub.Error(), "Missing PublisherId param cp", t) - // remove "cf" param and verify error message. - bidder.AdUnits[0].Params = json.RawMessage("{\"cp\": 2001, \"ct\": 1001}") - _, errSize := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errSize.Error(), "Missing AdSize param cf", t) - // invalid width parameter value for cf - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"aXb\"}") - _, errWidth := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errWidth.Error(), "Invalid Width param a", t) - // invalid parameter values for cf - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"12Xb\"}") - _, errHeight := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errHeight.Error(), "Invalid Height param b", t) - // invalid parameter values for cf - bidder.AdUnits[0].Params = json.RawMessage("{\"ct\": 1001, \"cp\": 2001, \"cf\": \"12-20\"}") - _, errAdSizeValue := adapter.Call(ctx, req, bidder) - adapterstest.VerifyStringValue(errAdSizeValue.Error(), "Invalid AdSize param 12-20", t) -} - -/** - * Verify the openrtb request sent to Pulsepoint endpoint. - * Ensure the ct, cp, cf params are transformed and sent alright. - */ -func TestPulsePointOpenRTBRequest(t *testing.T) { - service := CreateService(adapterstest.BidOnTags("")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(1, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - adapter.Call(ctx, req, bidder) - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) - adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "1001", t) - adapterstest.VerifyStringValue(service.LastBidRequest.Site.Publisher.ID, "2001", t) - adapterstest.VerifyBannerSize(service.LastBidRequest.Imp[0].Banner, 728, 90, t) -} - -/** - * Verify bidding behavior. - */ -func TestPulsePointBiddingBehavior(t *testing.T) { - // setup server endpoint to return bid. - server := CreateService(adapterstest.BidOnTags("1001")).Server - ctx := context.TODO() - req := SampleRequest(1, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // number of bids should be 1 - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[0].BidderCode, "pulsepoint", t) - adapterstest.VerifyStringValue(bids[0].Adm, "
This is an Ad
", t) - adapterstest.VerifyStringValue(bids[0].Creative_id, "Cr-234", t) - adapterstest.VerifyIntValue(int(bids[0].Width), 728, t) - adapterstest.VerifyIntValue(int(bids[0].Height), 90, t) - adapterstest.VerifyIntValue(int(bids[0].Price*100), 210, t) - adapterstest.VerifyStringValue(bids[0].CreativeMediaType, string(openrtb_ext.BidTypeBanner), t) -} - -/** - * Verify bidding behavior on multiple impressions, some impressions make a bid - */ -func TestPulsePointMultiImpPartialBidding(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("1001")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(2, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) -} - -/** - * Verify bidding behavior on multiple impressions, all impressions passed back. - */ -func TestPulsePointMultiImpPassback(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(2, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 0, t) -} - -/** - * Verify bidding behavior on multiple impressions, all impressions passed back. - */ -func TestPulsePointMultiImpAllBid(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("1001,1002")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(2, t) - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 2, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[1].AdUnitCode, "div-adunit-2", t) -} - -/** - * Verify bidding behavior on mobile app requests - */ -func TestMobileAppRequest(t *testing.T) { - // setup server endpoint to return bid. - service := CreateService(adapterstest.BidOnTags("1001")) - server := service.Server - ctx := context.TODO() - req := SampleRequest(1, t) - req.App = &openrtb2.App{ - ID: "com.facebook.katana", - Name: "facebook", - } - bidder := req.Bidders[0] - adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // one mobile app impression sent. - // verify appropriate fields are sent to pulsepoint endpoint. - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) - adapterstest.VerifyStringValue(service.LastBidRequest.App.ID, "com.facebook.katana", t) - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) -} - -/** - * Produces a sample PBSRequest, for the impressions given. - */ -func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { - // create a request object - req := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 2), - } - req.AccountID = "1" - tagId := 1001 - for i := 0; i < numberOfImpressions; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb2.Format{ - { - W: 10, - H: 12, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "pulsepoint", - BidID: fmt.Sprintf("Bid-%d", i+1), - Params: json.RawMessage(fmt.Sprintf("{\"ct\": %d, \"cp\": 2001, \"cf\": \"728X90\"}", tagId+i)), - }, - }, - } - } - // serialize the request to json - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(req) - if err != nil { - t.Fatalf("Error when serializing request") - } - // setup a http request - httpReq := httptest.NewRequest("POST", CreateService(adapterstest.BidOnTags("")).Server.URL, body) - httpReq.Header.Add("Referer", "http://news.pub/topnews") - pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) - pc.TrySync("pulsepoint", "pulsepointUser123") - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - // parse the http request - cacheClient, _ := dummycache.New() - hcs := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcs) - if err != nil { - t.Fatalf("Error when parsing request: %v", err) - } - return parsedReq -} - -/** - * Represents a mock ORTB endpoint of PulsePoint. Would return a bid - * for TagId 1001 and passback for 1002 as the default behavior. - */ -func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { - service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb2.BidRequest - server := httptest.NewServer(http.HandlerFunc(func(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 - } - lastBidRequest = breq - var bids []openrtb2.Bid - for i, imp := range breq.Imp { - if tagsToBid[imp.TagID] { - bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) - } - } - // no bids were produced, pulsepoint service returns 204 - if len(bids) == 0 { - w.WriteHeader(204) - } else { - // serialize the bids to openrtb2.BidResponse - js, _ := json.Marshal(openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: bids, - }, - }, - }) - w.Header().Set("Content-Type", "application/json") - w.Write(js) - } - })) - service.Server = server - service.LastBidRequest = &lastBidRequest - return service -} - -///////////////////////////////// -// Legacy implementation: End -///////////////////////////////// diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 80c62df16a1..ace1bfaa12d 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -1,53 +1,30 @@ package rubicon import ( - "bytes" - "context" "encoding/json" "fmt" - "github.com/buger/jsonparser" - "io/ioutil" "net/http" "net/url" "strconv" "strings" - "github.com/golang/glog" - "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" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const badvLimitSize = 50 type RubiconAdapter struct { - http *adapters.HTTPAdapter URI string XAPIUsername string XAPIPassword string } -func (a *RubiconAdapter) Name() string { - return "rubicon" -} - -func (a *RubiconAdapter) SkipNoCookies() bool { - return false -} - -type rubiconParams struct { - AccountId int `json:"accountId"` - SiteId int `json:"siteId"` - ZoneId int `json:"zoneId"` - Inventory json.RawMessage `json:"inventory,omitempty"` - Visitor json.RawMessage `json:"visitor,omitempty"` - Video rubiconVideoParams `json:"video"` -} - type bidRequestExt struct { Prebid bidRequestExtPrebid `json:"prebid"` } @@ -134,15 +111,6 @@ type rubiconBannerExt struct { } // ***** Video Extension ***** -type rubiconVideoParams struct { - Language string `json:"language,omitempty"` - PlayerHeight int `json:"playerHeight,omitempty"` - PlayerWidth int `json:"playerWidth,omitempty"` - VideoSizeID int `json:"size_id,omitempty"` - Skip int `json:"skip,omitempty"` - SkipDelay int `json:"skipdelay,omitempty"` -} - type rubiconVideoExt struct { Skip int `json:"skip,omitempty"` SkipDelay int `json:"skipdelay,omitempty"` @@ -154,19 +122,6 @@ type rubiconVideoExtRP struct { SizeID int `json:"size_id,omitempty"` } -type rubiconTargetingExt struct { - RP rubiconTargetingExtRP `json:"rp"` -} - -type rubiconTargetingExtRP struct { - Targeting []rubiconTargetingObj `json:"targeting"` -} - -type rubiconTargetingObj struct { - Key string `json:"key"` - Values []string `json:"values"` -} - type rubiconDeviceExtRP struct { PixelRatio float64 `json:"pixelratio"` } @@ -175,10 +130,6 @@ type rubiconDeviceExt struct { RP rubiconDeviceExtRP `json:"rp"` } -type rubiconUser struct { - Language string `json:"language"` -} - type rubiconBidResponse struct { openrtb2.BidResponse SeatBid []rubiconSeatBid `json:"seatbid,omitempty"` @@ -353,273 +304,6 @@ func parseRubiconSizes(sizes []openrtb2.Format) (primary int, alt []int, err err return } -func (a *RubiconAdapter) callOne(ctx context.Context, 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") - httpReq.Header.Add("User-Agent", "prebid-server/1.0") - httpReq.SetBasicAuth(a.XAPIUsername, a.XAPIPassword) - - rubiResp, e := ctxhttp.Do(ctx, a.http.Client, httpReq) - if e != nil { - err = e - return - } - - defer rubiResp.Body.Close() - body, _ := ioutil.ReadAll(rubiResp.Body) - result.ResponseBody = string(body) - - result.StatusCode = rubiResp.StatusCode - - if rubiResp.StatusCode == 204 { - return - } - - if rubiResp.StatusCode == http.StatusBadRequest { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), - } - } - - if rubiResp.StatusCode != http.StatusOK { - err = &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), - } - return - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - err = &errortypes.BadServerResponse{ - Message: err.Error(), - } - return - } - if len(bidResp.SeatBid) == 0 { - return - } - if len(bidResp.SeatBid[0].Bid) == 0 { - return - } - bid := bidResp.SeatBid[0].Bid[0] - - result.Bid = &pbs.PBSBid{ - AdUnitCode: bid.ImpID, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - // for video, the width and height are undefined as there's no corresponding return value from XAPI - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - - // Pull out any server-side determined targeting - var rpExtTrg rubiconTargetingExt - - if err := json.Unmarshal([]byte(bid.Ext), &rpExtTrg); err == nil { - // Converting string => array(string) to string => string - targeting := make(map[string]string) - - // Only pick off the first for now - for _, target := range rpExtTrg.RP.Targeting { - targeting[target.Key] = target.Values[0] - } - - result.Bid.AdServerTargeting = targeting - } - - return -} - -type callOneObject struct { - requestJson bytes.Buffer - mediaType pbs.MediaType -} - -func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - callOneObjects := make([]callOneObject, 0, len(bidder.AdUnits)) - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - - rubiReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), supportedMediaTypes) - if err != nil { - return nil, err - } - - rubiReqImpCopy := rubiReq.Imp - - for i, unit := range bidder.AdUnits { - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(rubiReqImpCopy) <= i { - break - } - // Only grab this ad unit - // Not supporting multi-media-type add-unit yet - thisImp := rubiReqImpCopy[i] - - // Amend it with RP-specific information - var params rubiconParams - err = json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - - var mint, mintVersion string - mint = "prebid" - mintVersion = req.SDK.Source + "_" + req.SDK.Platform + "_" + req.SDK.Version - track := rubiconImpExtRPTrack{Mint: mint, MintVersion: mintVersion} - - impExt := rubiconImpExt{RP: rubiconImpExtRP{ - ZoneID: params.ZoneId, - Target: params.Inventory, - Track: track, - }} - thisImp.Ext, err = json.Marshal(&impExt) - if err != nil { - continue - } - - // Copy the $.user object and amend with $.user.ext.rp.target - // Copy avoids race condition since it points to ref & shared with other adapters - userCopy := *rubiReq.User - userExt := rubiconUserExt{RP: rubiconUserExtRP{Target: params.Visitor}} - userCopy.Ext, err = json.Marshal(&userExt) - // Assign back our copy - rubiReq.User = &userCopy - - deviceCopy := *rubiReq.Device - deviceExt := rubiconDeviceExt{RP: rubiconDeviceExtRP{PixelRatio: rubiReq.Device.PxRatio}} - deviceCopy.Ext, err = json.Marshal(&deviceExt) - rubiReq.Device = &deviceCopy - - if thisImp.Video != nil { - - videoSizeId := params.Video.VideoSizeID - if videoSizeId == 0 { - resolvedSizeId, err := resolveVideoSizeId(thisImp.Video.Placement, thisImp.Instl, thisImp.ID) - if err == nil { - videoSizeId = resolvedSizeId - } else { - continue - } - } - - videoExt := rubiconVideoExt{Skip: params.Video.Skip, SkipDelay: params.Video.SkipDelay, RP: rubiconVideoExtRP{SizeID: videoSizeId}} - thisImp.Video.Ext, err = json.Marshal(&videoExt) - } else { - primarySizeID, altSizeIDs, err := parseRubiconSizes(unit.Sizes) - if err != nil { - continue - } - bannerExt := rubiconBannerExt{RP: rubiconBannerExtRP{SizeID: primarySizeID, AltSizeIDs: altSizeIDs, MIME: "text/html"}} - thisImp.Banner.Ext, err = json.Marshal(&bannerExt) - } - - siteExt := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: params.SiteId}} - pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: params.AccountId}} - var rubiconUser rubiconUser - err = json.Unmarshal(req.PBSUser, &rubiconUser) - - if rubiReq.Site != nil { - siteCopy := *rubiReq.Site - siteCopy.Ext, err = json.Marshal(&siteExt) - siteCopy.Publisher = &openrtb2.Publisher{} - siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) - siteCopy.Content = &openrtb2.Content{} - siteCopy.Content.Language = rubiconUser.Language - rubiReq.Site = &siteCopy - } else { - site := &openrtb2.Site{} - site.Content = &openrtb2.Content{} - site.Content.Language = rubiconUser.Language - rubiReq.Site = site - } - - if rubiReq.App != nil { - appCopy := *rubiReq.App - appCopy.Ext, err = json.Marshal(&siteExt) - appCopy.Publisher = &openrtb2.Publisher{} - appCopy.Publisher.Ext, err = json.Marshal(&pubExt) - rubiReq.App = &appCopy - } - - rubiReq.Imp = []openrtb2.Imp{thisImp} - - var reqBuffer bytes.Buffer - err = json.NewEncoder(&reqBuffer).Encode(rubiReq) - if err != nil { - return nil, err - } - callOneObjects = append(callOneObjects, callOneObject{reqBuffer, unit.MediaTypes[0]}) - } - if len(callOneObjects) == 0 { - return nil, &errortypes.BadInput{ - Message: "Invalid ad unit/imp", - } - } - - ch := make(chan adapters.CallOneResult) - for _, obj := range callOneObjects { - go func(bidder *pbs.PBSBidder, reqJSON bytes.Buffer, mediaType pbs.MediaType) { - result, err := a.callOne(ctx, 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 - } else { - // no need to check whether mediaTypes is nil or length of zero, pbs.ParsePBSRequest will cover - // these cases. - // for media types other than banner and video, pbs.ParseMediaType will throw error. - // we may want to create a map/switch cases to support more media types in the future. - if mediaType == pbs.MEDIA_TYPE_VIDEO { - result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeVideo) - } else { - result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeBanner) - } - } - } - ch <- result - }(bidder, obj.requestJson, obj.mediaType) - } - - bids := make(pbs.PBSBidSlice, 0) - for i := 0; i < len(callOneObjects); i++ { - result := <-ch - if result.Bid != nil && result.Bid.Price != 0 { - bids = append(bids, result.Bid) - } - if req.IsDebug { - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - RequestBody: callOneObjects[i].requestJson.String(), - StatusCode: result.StatusCode, - ResponseBody: result.ResponseBody, - } - bidder.Debug = append(bidder.Debug, debug) - } - if result.Error != nil { - if glog.V(2) { - glog.Infof("Error from rubicon adapter: %v", result.Error) - } - err = result.Error - } - } - - if len(bids) == 0 { - return nil, err - } - return bids, nil -} - func resolveVideoSizeId(placement openrtb2.VideoPlacementType, instl int8, impId string) (sizeID int, err error) { if placement != 0 { if placement == 1 { @@ -665,19 +349,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func NewRubiconLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, uri string, xuser string, xpass string, tracker string) *RubiconAdapter { - a := adapters.NewHTTPAdapter(httpConfig) - - uri = appendTrackerToUrl(uri, tracker) - - return &RubiconAdapter{ - http: a, - URI: uri, - XAPIUsername: xuser, - XAPIPassword: xpass, - } -} - func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) errs := make([]error, 0, len(request.Imp)) diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 9bfa04fa78f..0d88937b6da 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1,27 +1,17 @@ package rubicon import ( - "bytes" - "context" "encoding/json" "errors" - "fmt" - "io/ioutil" "net/http" - "net/http/httptest" "strconv" - "strings" "testing" - "time" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -48,291 +38,17 @@ type rubiSetNetworkIdTestScenario struct { isNetworkIdSet bool } -type rubiTagInfo struct { - code string - zoneID int - bid float64 - content string - adServerTargeting map[string]string - mediaType string -} - type rubiBidInfo struct { - domain string - page string - accountID int - siteID int - tags []rubiTagInfo - deviceIP string - deviceUA string - buyerUID string - xapiuser string - xapipass string - delay time.Duration - visitorTargeting string - inventoryTargeting string - sdkVersion string - sdkPlatform string - sdkSource string - devicePxRatio float64 + domain string + page string + deviceIP string + deviceUA string + buyerUID string + devicePxRatio float64 } var rubidata rubiBidInfo -func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { - defer func() { - err := r.Body.Close() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }() - - 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 len(breq.Imp) > 1 { - http.Error(w, "Rubicon adapter only supports one Imp per request", http.StatusInternalServerError) - return - } - imp := breq.Imp[0] - var rix rubiconImpExt - err = json.Unmarshal(imp.Ext, &rix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - impTargetingString, _ := json.Marshal(&rix.RP.Target) - if string(impTargetingString) != rubidata.inventoryTargeting { - http.Error(w, fmt.Sprintf("Inventory FPD targeting '%s' doesn't match '%s'", string(impTargetingString), rubidata.inventoryTargeting), http.StatusInternalServerError) - return - } - if rix.RP.Track.Mint != "prebid" { - http.Error(w, fmt.Sprintf("Track mint '%s' doesn't match '%s'", rix.RP.Track.Mint, "prebid"), http.StatusInternalServerError) - return - } - mintVersionString := rubidata.sdkSource + "_" + rubidata.sdkPlatform + "_" + rubidata.sdkVersion - if rix.RP.Track.MintVersion != mintVersionString { - http.Error(w, fmt.Sprintf("Track mint version '%s' doesn't match '%s'", rix.RP.Track.MintVersion, mintVersionString), http.StatusInternalServerError) - return - } - - ix := -1 - - for i, tag := range rubidata.tags { - if rix.RP.ZoneID == tag.zoneID { - ix = i - } - } - if ix == -1 { - http.Error(w, fmt.Sprintf("Zone %d not found", rix.RP.ZoneID), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 2), - }, - }, - } - - if imp.Banner != nil { - var bix rubiconBannerExt - err = json.Unmarshal(imp.Banner.Ext, &bix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if bix.RP.SizeID != 15 { // 300x250 - http.Error(w, fmt.Sprintf("Primary size ID isn't 15"), http.StatusInternalServerError) - return - } - if len(bix.RP.AltSizeIDs) != 1 || bix.RP.AltSizeIDs[0] != 10 { // 300x600 - http.Error(w, fmt.Sprintf("Alt size ID isn't 10"), http.StatusInternalServerError) - return - } - if bix.RP.MIME != "text/html" { - http.Error(w, fmt.Sprintf("MIME isn't text/html"), http.StatusInternalServerError) - return - } - } - - if imp.Video != nil { - var vix rubiconVideoExt - err = json.Unmarshal(imp.Video.Ext, &vix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if len(imp.Video.MIMEs) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.mimes array"), http.StatusInternalServerError) - return - } - if len(imp.Video.Protocols) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.protocols array"), http.StatusInternalServerError) - return - } - for _, protocol := range imp.Video.Protocols { - if protocol < 1 || protocol > 8 { - http.Error(w, fmt.Sprintf("Invalid video protocol %d", protocol), http.StatusInternalServerError) - return - } - } - } - - targeting := "{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}" - rawTargeting := json.RawMessage(targeting) - - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "random-id", - ImpID: imp.ID, - Price: rubidata.tags[ix].bid, - AdM: rubidata.tags[ix].content, - Ext: rawTargeting, - } - - if breq.Site == nil { - http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) - return - } - if breq.Site.Domain != rubidata.domain { - http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, rubidata.domain), http.StatusInternalServerError) - return - } - if breq.Site.Page != rubidata.page { - http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, rubidata.page), http.StatusInternalServerError) - return - } - var rsx rubiconSiteExt - err = json.Unmarshal(breq.Site.Ext, &rsx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if rsx.RP.SiteID != rubidata.siteID { - http.Error(w, fmt.Sprintf("SiteID '%d' doesn't match '%d", rsx.RP.SiteID, rubidata.siteID), http.StatusInternalServerError) - return - } - if breq.Site.Publisher == nil { - http.Error(w, fmt.Sprintf("No site.publisher object sent"), http.StatusInternalServerError) - return - } - var rpx rubiconPubExt - err = json.Unmarshal(breq.Site.Publisher.Ext, &rpx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if rpx.RP.AccountID != rubidata.accountID { - http.Error(w, fmt.Sprintf("AccountID '%d' doesn't match '%d'", rpx.RP.AccountID, rubidata.accountID), http.StatusInternalServerError) - return - } - if breq.Device.UA != rubidata.deviceUA { - http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s'", breq.Device.UA, rubidata.deviceUA), http.StatusInternalServerError) - return - } - if breq.Device.IP != rubidata.deviceIP { - http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s'", breq.Device.IP, rubidata.deviceIP), http.StatusInternalServerError) - return - } - if breq.Device.PxRatio != rubidata.devicePxRatio { - http.Error(w, fmt.Sprintf("Pixel ratio '%f' doesn't match '%f'", breq.Device.PxRatio, rubidata.devicePxRatio), http.StatusInternalServerError) - return - } - if breq.User.BuyerUID != rubidata.buyerUID { - http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s'", breq.User.BuyerUID, rubidata.buyerUID), http.StatusInternalServerError) - return - } - - var rux rubiconUserExt - err = json.Unmarshal(breq.User.Ext, &rux) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - userTargetingString, _ := json.Marshal(&rux.RP.Target) - if string(userTargetingString) != rubidata.visitorTargeting { - http.Error(w, fmt.Sprintf("User FPD targeting '%s' doesn't match '%s'", string(userTargetingString), rubidata.visitorTargeting), http.StatusInternalServerError) - return - } - - if rubidata.delay > 0 { - <-time.After(rubidata.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 TestRubiconBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.Nil(t, err, "Should not have gotten an error: %v", err) - assert.Equal(t, 2, len(bids), "Received %d bids instead of 3", len(bids)) - - for _, bid := range bids { - matched := false - for _, tag := range rubidata.tags { - if bid.AdUnitCode == tag.code { - matched = true - - assert.Equal(t, "rubicon", bid.BidderCode, "Incorrect BidderCode '%s'", bid.BidderCode) - - assert.Equal(t, tag.bid, bid.Price, "Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) - - assert.Equal(t, tag.content, bid.Adm, "Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - - assert.Equal(t, bid.AdServerTargeting, tag.adServerTargeting, - "Incorrect targeting '%+v' expected '%+v'", bid.AdServerTargeting, tag.adServerTargeting) - - assert.Equal(t, tag.mediaType, bid.CreativeMediaType, "Incorrect media type '%s' expected '%s'", bid.CreativeMediaType, tag.mediaType) - } - } - assert.True(t, matched, "Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - - // same test but with request timing out - rubidata.delay = 20 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - bids, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten a timeout error: %v", err) -} - -func TestRubiconUserSyncInfo(t *testing.T) { - conf := *adapters.DefaultHTTPAdapterConfig - an := NewRubiconLegacyAdapter(&conf, "uri", "xuser", "xpass", "pbs-test-tracker") - - assert.Equal(t, "rubicon", an.Name(), "Name '%s' != 'rubicon'", an.Name()) - - assert.False(t, an.SkipNoCookies(), "SkipNoCookies should be false") -} - func getTestSizes() map[int]openrtb2.Format { return map[int]openrtb2.Format{ 15: {W: 300, H: 250}, @@ -703,368 +419,6 @@ func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 { 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) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Equal(t, 204, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 204 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestNotFoundResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Equal(t, 404, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 404 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "HTTP status 404"), - "Should start with 'HTTP status' instead of: %v", err.Error()) -} - -func TestWrongFormatResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("This is text.")) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Equal(t, 200, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 200 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "invalid character"), - "Should start with 'invalid character' instead of: %v", err) -} - -func TestZeroSeatBidResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{}, - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestEmptyBidResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 0), - }, - }, - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestWrongBidIdResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 2), - }, - }, - } - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "random-id", - ImpID: "zma", - Price: 1.67, - AdM: "zma", - Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.NotNil(t, err, "Should not have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "Unknown ad unit code"), - "Should start with 'Unknown ad unit code' instead of: %v", err) -} - -func TestZeroPriceBidResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 1), - }, - }, - } - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "test-bid-id", - ImpID: "first-tag", - Price: 0, - AdM: "zma", - Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Nil(t, b, "\n\n\n0 price bids are being included %d, err : %v", len(b), err) -} - -func TestDifferentRequest(t *testing.T) { - SIZE_ID := getTestSizes() - server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - - // test app not nil - pbReq.App = &openrtb2.App{ - ID: "com.test", - Name: "testApp", - } - - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set app back to normal - pbReq.App = nil - - // test video media type - pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO} - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set media back to normal - pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - - // test wrong params - pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %s, \"siteId\": %d, \"visitor\": %s, \"inventory\": %s}", "zma", rubidata.siteID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set params back to normal - pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", 8394, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) - - // test invalid size - pbReq.Bidders[0].AdUnits[0].Sizes = []openrtb2.Format{ - { - W: 2222, - H: 333, - }, - } - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ - { - W: 222, - H: 3333, - }, - { - W: 350, - H: 270, - }, - } - pbReq.Bidders[0].AdUnits = pbReq.Bidders[0].AdUnits[:len(pbReq.Bidders[0].AdUnits)-1] - b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ - { - W: 222, - H: 3333, - }, - SIZE_ID[10], - SIZE_ID[15], - } - b, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.Nil(t, err, "Should have not gotten an error: %v", err) - - assert.Equal(t, 1, len(b), - "Filtering bids based on ad unit sizes failed. Got %d bids instead of 1, error = %v", len(b), err) -} - -func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdapter, ctx context.Context, pbReq *pbs.PBSRequest) { - SIZE_ID := getTestSizes() - rubidata = rubiBidInfo{ - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - accountID: 7891, - siteID: 283282, - tags: make([]rubiTagInfo, 3), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "need-an-actual-rp-id", - visitorTargeting: "[\"v1\",\"v2\"]", - inventoryTargeting: "[\"i1\",\"i2\"]", - sdkVersion: "2.0.0", - sdkPlatform: "iOS", - sdkSource: "some-sdk", - devicePxRatio: 4.0, - } - - targeting := make(map[string]string, 2) - targeting["key1"] = "value1" - targeting["key2"] = "value2" - - rubidata.tags[0] = rubiTagInfo{ - code: "first-tag", - zoneID: 8394, - bid: 1.67, - adServerTargeting: targeting, - mediaType: "banner", - } - rubidata.tags[1] = rubiTagInfo{ - code: "second-tag", - zoneID: 8395, - bid: 3.22, - adServerTargeting: targeting, - mediaType: "banner", - } - rubidata.tags[2] = rubiTagInfo{ - code: "video-tag", - zoneID: 7780, - bid: 23.12, - adServerTargeting: targeting, - mediaType: "video", - } - - conf := *adapters.DefaultHTTPAdapterConfig - an = NewRubiconLegacyAdapter(&conf, "uri", rubidata.xapiuser, rubidata.xapipass, "pbs-test-tracker") - an.URI = server.URL - - pbin := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 3), - Device: &openrtb2.Device{PxRatio: rubidata.devicePxRatio}, - SDK: &pbs.SDK{Source: rubidata.sdkSource, Platform: rubidata.sdkPlatform, Version: rubidata.sdkVersion}, - } - - for i, tag := range rubidata.tags { - pbin.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb2.Format{ - SIZE_ID[10], - SIZE_ID[15], - }, - Bids: []pbs.Bids{ - { - BidderCode: "rubicon", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", tag.zoneID, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)), - }, - }, - } - if tag.mediaType == "video" { - pbin.AdUnits[i].Video = pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{1, 2, 3, 4, 5}, - } - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbin) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - - req := httptest.NewRequest("POST", server.URL, body) - req.Header.Add("Referer", rubidata.page) - req.Header.Add("User-Agent", rubidata.deviceUA) - req.Header.Add("X-Real-IP", rubidata.deviceIP) - - pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) - pc.TrySync("rubicon", rubidata.buyerUID) - 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) - pbReq.IsDebug = true - - assert.Nil(t, err, "ParsePBSRequest failed: %v", err) - - assert.Equal(t, 1, len(pbReq.Bidders), - "ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - - assert.Equal(t, "rubicon", pbReq.Bidders[0].BidderCode, - "ParsePBSRequest returned invalid bidder") - - ctx = context.TODO() - return -} - func TestOpenRTBRequest(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) diff --git a/adapters/sharethrough/utils_test.go b/adapters/sharethrough/utils_test.go index b842cf0b0c0..70e97947880 100644 --- a/adapters/sharethrough/utils_test.go +++ b/adapters/sharethrough/utils_test.go @@ -151,10 +151,6 @@ type userAgentTest struct { expected bool } -type userAgentFailureTest struct { - input string -} - func runUserAgentTests(tests map[string]userAgentTest, fn func(string) bool, t *testing.T) { for testName, test := range tests { t.Logf("Test case: %s\n", testName) diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go index 690d5f59f67..0ff71cdb0e5 100644 --- a/adapters/sonobi/sonobi.go +++ b/adapters/sonobi/sonobi.go @@ -25,10 +25,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -type sonobiParams struct { - TagID string `json:"TagID"` -} - // MakeRequests Makes the OpenRTB request payload func (a *SonobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error @@ -152,9 +148,3 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), } } - -func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { - if len(headerValue) > 0 { - headers.Add(headerName, headerValue) - } -} diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 40969d3638e..98264ce3a1b 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -1,176 +1,23 @@ package sovrn import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "net/url" - "sort" "strconv" "strings" - "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" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + + "github.com/mxmCherry/openrtb/v15/openrtb2" ) type SovrnAdapter struct { - http *adapters.HTTPAdapter - URI string -} - -// Name - export adapter name */ -func (s *SovrnAdapter) Name() string { - return "sovrn" -} - -// FamilyName used for cookies and such -func (s *SovrnAdapter) FamilyName() string { - return "sovrn" -} - -func (s *SovrnAdapter) SkipNoCookies() bool { - return false -} - -// Call send bid requests to sovrn and receive responses -func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - sReq, err := adapters.MakeOpenRTBGeneric(req, bidder, s.FamilyName(), supportedMediaTypes) - - if err != nil { - return nil, err - } - - sovrnReq := openrtb2.BidRequest{ - ID: sReq.ID, - Imp: sReq.Imp, - Site: sReq.Site, - User: sReq.User, - Regs: sReq.Regs, - } - - // add tag ids to impressions - for i, unit := range bidder.AdUnits { - var params openrtb_ext.ExtImpSovrn - err = json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, err - } - - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(sovrnReq.Imp) <= i { - break - } - sovrnReq.Imp[i].TagID = getTagid(params) - } - - reqJSON, err := json.Marshal(sovrnReq) - if err != nil { - return nil, err - } - - debug := &pbs.BidderDebug{ - RequestURI: s.URI, - } - - httpReq, _ := http.NewRequest("POST", s.URI, bytes.NewReader(reqJSON)) - httpReq.Header.Set("Content-Type", "application/json") - if sReq.Device != nil { - addHeaderIfNonEmpty(httpReq.Header, "User-Agent", sReq.Device.UA) - addHeaderIfNonEmpty(httpReq.Header, "X-Forwarded-For", sReq.Device.IP) - addHeaderIfNonEmpty(httpReq.Header, "Accept-Language", sReq.Device.Language) - if sReq.Device.DNT != nil { - addHeaderIfNonEmpty(httpReq.Header, "DNT", strconv.Itoa(int(*sReq.Device.DNT))) - } - } - if sReq.User != nil { - userID := strings.TrimSpace(sReq.User.BuyerUID) - if len(userID) > 0 { - httpReq.AddCookie(&http.Cookie{Name: "ljt_reader", Value: userID}) - } - } - sResp, err := ctxhttp.Do(ctx, s.http.Client, httpReq) - if err != nil { - return nil, err - } - defer sResp.Body.Close() - - debug.StatusCode = sResp.StatusCode - - if sResp.StatusCode == http.StatusNoContent { - return nil, nil - } - - body, err := ioutil.ReadAll(sResp.Body) - if err != nil { - return nil, err - } - responseBody := string(body) - - if sResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", sResp.StatusCode, responseBody), - } - } - - if sResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", sResp.StatusCode, responseBody), - } - } - - if req.IsDebug { - debug.RequestBody = string(reqJSON) - bidder.Debug = append(bidder.Debug, debug) - debug.ResponseBody = responseBody - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - adm, _ := url.QueryUnescape(bid.AdM) - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: adm, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - NURL: bid.NURL, - } - bids = append(bids, &pbid) - } - } - - sort.Sort(bids) - return bids, nil + URI string } func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { @@ -303,14 +150,6 @@ func getTagid(sovrnExt openrtb_ext.ExtImpSovrn) string { } } -// NewSovrnLegacyAdapter create a new SovrnAdapter instance -func NewSovrnLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *SovrnAdapter { - return &SovrnAdapter{ - http: adapters.NewHTTPAdapter(config), - URI: endpoint, - } -} - // Builder builds a new instance of the Sovrn adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { bidder := &SovrnAdapter{ diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index 407c505437a..49ed52844f3 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -1,28 +1,11 @@ package sovrn import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http/httptest" "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "context" - "net/http" - - "strconv" - "time" - - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/cache/dummycache" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestJsonSamples(t *testing.T) { @@ -35,262 +18,3 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "sovrntest", bidder) } - -// ---------------------------------------------------------------------------- -// Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb2. - -var testSovrnUserId = "SovrnUser123" -var testUserAgent = "user-agent-test" -var testUrl = "http://news.pub/topnews" -var testIp = "123.123.123.123" - -func TestSovrnAdapterNames(t *testing.T) { - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://sovrn/rtb/bid") - adapterstest.VerifyStringValue(adapter.Name(), "sovrn", t) - adapterstest.VerifyStringValue(adapter.FamilyName(), "sovrn", t) -} - -func TestSovrnAdapter_SkipNoCookies(t *testing.T) { - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "http://sovrn/rtb/bid") - adapterstest.VerifyBoolValue(adapter.SkipNoCookies(), false, t) -} - -func TestSovrnOpenRtbRequest(t *testing.T) { - service := CreateSovrnService(adapterstest.BidOnTags("")) - server := service.Server - ctx := context.Background() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - adapter.Call(ctx, req, bidder) - - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t) - adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "123456", t) - adapterstest.VerifyBannerSize(service.LastBidRequest.Imp[0].Banner, 728, 90, t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -func TestSovrnBiddingBehavior(t *testing.T) { - service := CreateSovrnService(adapterstest.BidOnTags("123456")) - server := service.Server - ctx := context.TODO() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[0].BidderCode, "sovrn", t) - adapterstest.VerifyStringValue(bids[0].Adm, "
This is an Ad
", t) - adapterstest.VerifyStringValue(bids[0].Creative_id, "Cr-234", t) - adapterstest.VerifyIntValue(int(bids[0].Width), 728, t) - adapterstest.VerifyIntValue(int(bids[0].Height), 90, t) - adapterstest.VerifyIntValue(int(bids[0].Price*100), 210, t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -/** - * Verify bidding behavior on multiple impressions, some impressions make a bid - */ -func TestSovrntMultiImpPartialBidding(t *testing.T) { - // setup server endpoint to return bid. - service := CreateSovrnService(adapterstest.BidOnTags("123456")) - server := service.Server - ctx := context.TODO() - req := SampleSovrnRequest(2, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 1, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -/** - * Verify bidding behavior on multiple impressions, all impressions passed back. - */ -func TestSovrnMultiImpAllBid(t *testing.T) { - // setup server endpoint to return bid. - service := CreateSovrnService(adapterstest.BidOnTags("123456,123457")) - server := service.Server - ctx := context.TODO() - req := SampleSovrnRequest(2, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - bids, _ := adapter.Call(ctx, req, bidder) - // two impressions sent. - // number of bids should be 1 - adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 2, t) - adapterstest.VerifyIntValue(len(bids), 2, t) - adapterstest.VerifyStringValue(bids[0].AdUnitCode, "div-adunit-1", t) - adapterstest.VerifyStringValue(bids[1].AdUnitCode, "div-adunit-2", t) - checkHttpRequest(*service.LastHttpRequest, t) -} - -func checkHttpRequest(req http.Request, t *testing.T) { - adapterstest.VerifyStringValue(req.Header.Get("Accept-Language"), "murican", t) - var cookie, _ = req.Cookie("ljt_reader") - adapterstest.VerifyStringValue((*cookie).Value, testSovrnUserId, t) - adapterstest.VerifyStringValue(req.Header.Get("User-Agent"), testUserAgent, t) - adapterstest.VerifyStringValue(req.Header.Get("Content-Type"), "application/json", t) - adapterstest.VerifyStringValue(req.Header.Get("X-Forwarded-For"), testIp, t) - adapterstest.VerifyStringValue(req.Header.Get("DNT"), "0", t) -} - -func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { - dnt := int8(0) - device := openrtb2.Device{ - Language: "murican", - DNT: &dnt, - } - - user := openrtb2.User{ - ID: testSovrnUserId, - } - - req := pbs.PBSRequest{ - AccountID: "1", - AdUnits: make([]pbs.AdUnit, 2), - Device: &device, - User: &user, - } - - tagID := 123456 - - for i := 0; i < numberOfImpressions; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb2.Format{ - { - W: 728, - H: 90, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "sovrn", - BidID: fmt.Sprintf("Bid-%d", i+1), - Params: json.RawMessage(fmt.Sprintf("{\"tagid\": \"%s\" }", strconv.Itoa(tagID+i))), - }, - }, - } - - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(req) - if err != nil { - t.Fatalf("Error when serializing request") - } - - httpReq := httptest.NewRequest("POST", CreateSovrnService(adapterstest.BidOnTags("")).Server.URL, body) - httpReq.Header.Add("Referer", testUrl) - httpReq.Header.Add("User-Agent", testUserAgent) - httpReq.Header.Add("X-Forwarded-For", testIp) - pc := usersync.ParseCookieFromRequest(httpReq, &config.HostCookie{}) - pc.TrySync("sovrn", testSovrnUserId) - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - // parse the http request - cacheClient, _ := dummycache.New() - hcc := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcc) - if err != nil { - t.Fatalf("Error when parsing request: %v", err) - } - return parsedReq - -} - -func TestNoContentResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) - })) - defer server.Close() - - ctx := context.TODO() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - _, err := adapter.Call(ctx, req, bidder) - - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - -} - -func TestNotFoundResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - defer server.Close() - - ctx := context.TODO() - req := SampleSovrnRequest(1, t) - bidder := req.Bidders[0] - adapter := NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - _, err := adapter.Call(ctx, req, bidder) - - adapterstest.VerifyStringValue(err.Error(), "HTTP status 404; body: ", t) - -} - -func CreateSovrnService(tagsToBid map[string]bool) adapterstest.OrtbMockService { - service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb2.BidRequest - var lastHttpReq http.Request - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - lastHttpReq = *r - 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 - } - lastBidRequest = breq - var bids []openrtb2.Bid - for i, imp := range breq.Imp { - if tagsToBid[imp.TagID] { - bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) - } - } - - // serialize the bids to openrtb2.BidResponse - js, _ := json.Marshal(openrtb2.BidResponse{ - SeatBid: []openrtb2.SeatBid{ - { - Bid: bids, - }, - }, - }) - w.Header().Set("Content-Type", "application/json") - w.Write(js) - })) - - service.Server = server - service.LastBidRequest = &lastBidRequest - service.LastHttpRequest = &lastHttpReq - - return service -} diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index a0721d98a2a..43853382354 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -102,8 +102,6 @@ func NewFileLogger(filename string) (analytics.PBSAnalyticsModule, error) { } } -type fileAuctionObject analytics.AuctionObject - func jsonifyAuctionObject(ao *analytics.AuctionObject) string { type alias analytics.AuctionObject b, err := json.Marshal(&struct { diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index cb8f088d0bf..0e0b3634508 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -2,59 +2,15 @@ package pubstack import ( "encoding/json" - "io/ioutil" "net/http" "net/http/httptest" - "os" "testing" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/stretchr/testify/assert" ) -func loadJSONFromFile() (*analytics.AuctionObject, error) { - req, err := os.Open("mocks/mock_openrtb_request.json") - if err != nil { - return nil, err - } - defer req.Close() - - reqCtn := openrtb2.BidRequest{} - reqPayload, err := ioutil.ReadAll(req) - if err != nil { - return nil, err - } - - err = json.Unmarshal(reqPayload, &reqCtn) - if err != nil { - return nil, err - } - - res, err := os.Open("mocks/mock_openrtb_response.json") - if err != nil { - return nil, err - } - defer res.Close() - - resCtn := openrtb2.BidResponse{} - resPayload, err := ioutil.ReadAll(res) - if err != nil { - return nil, err - } - - err = json.Unmarshal(resPayload, &resCtn) - if err != nil { - return nil, err - } - - return &analytics.AuctionObject{ - Request: &reqCtn, - Response: &resCtn, - }, nil -} - func TestPubstackModuleErrors(t *testing.T) { tests := []struct { description string diff --git a/cache/dummycache/dummycache.go b/cache/dummycache/dummycache.go deleted file mode 100644 index 02fe726d043..00000000000 --- a/cache/dummycache/dummycache.go +++ /dev/null @@ -1,65 +0,0 @@ -package dummycache - -import ( - "fmt" - - "github.com/prebid/prebid-server/cache" -) - -// Cache dummy config that will echo back results -type Cache struct { - accounts *accountService - config *configService -} - -// New creates new dummy.Cache -func New() (*Cache, error) { - return &Cache{ - accounts: &accountService{}, - config: &configService{}, - }, nil -} - -func (c *Cache) Accounts() cache.AccountsService { - return c.accounts -} -func (c *Cache) Config() cache.ConfigService { - return c.config -} - -// AccountService handles the account information -type accountService struct { -} - -// Get echos back the account -func (s *accountService) Get(id string) (*cache.Account, error) { - return &cache.Account{ - ID: id, - }, nil -} - -// ConfigService not supported, always returns an error -type configService struct { - c string -} - -// Get not supported, always returns an error -func (s *configService) Get(id string) (string, error) { - if s.c == "" { - return s.c, fmt.Errorf("No configuration provided") - } - return s.c, nil -} - -// Set will set a string in memory as the configuration -// this is so we can use it in tests such as pbs/pbsrequest_test.go -// it will ignore the id so this will pass tests -func (s *configService) Set(id, val string) error { - s.c = val - return nil -} - -// Close will always return nil -func (c *Cache) Close() error { - return nil -} diff --git a/cache/dummycache/dummycache_test.go b/cache/dummycache/dummycache_test.go deleted file mode 100644 index 74004feaa38..00000000000 --- a/cache/dummycache/dummycache_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package dummycache - -import "testing" - -func TestDummyCache(t *testing.T) { - - c, _ := New() - - account, err := c.Accounts().Get("account1") - if err != nil { - t.Fatal(err) - } - - if account.ID != "account1" { - t.Error("Wrong account returned") - } - - if err := c.Config().Set("config", "abc123"); err != nil { - t.Errorf("Dummy config should return nil") - } - - cfg, err := c.Config().Get("config") - if err != nil { - t.Error("Dummy configs should be supported") - } - - if cfg != "abc123" { - t.Error("Dummy config did not return back expected string") - } - -} diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go deleted file mode 100644 index 7bc4bea43f0..00000000000 --- a/cache/filecache/filecache.go +++ /dev/null @@ -1,123 +0,0 @@ -package filecache - -import ( - "fmt" - "io/ioutil" - - "github.com/golang/glog" - "github.com/prebid/prebid-server/cache" - "gopkg.in/yaml.v2" -) - -type shared struct { - Configs map[string]string - Accounts map[string]bool -} - -// Cache is a file backed cache -type Cache struct { - shared *shared - accounts *accountService - config *configService -} - -type fileConfig struct { - ID string `yaml:"id"` - Config string `yaml:"config"` -} - -type fileCacheFile struct { - Configs []fileConfig `yaml:"configs"` - Accounts []string `yaml:"accounts"` -} - -// New will load the file into memory -func New(filename string) (*Cache, error) { - if glog.V(2) { - glog.Infof("Reading inventory urls from %s", filename) - } - - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - if glog.V(2) { - glog.Infof("Parsing filecache YAML") - } - - var u fileCacheFile - if err = yaml.Unmarshal(b, &u); err != nil { - return nil, err - } - - if glog.V(2) { - glog.Infof("Building URL map") - } - - s := &shared{} - - s.Configs = make(map[string]string, len(u.Configs)) - for _, config := range u.Configs { - s.Configs[config.ID] = config.Config - } - glog.Infof("Loaded %d configs", len(u.Configs)) - - s.Accounts = make(map[string]bool, len(u.Accounts)) - for _, Account := range u.Accounts { - s.Accounts[Account] = true - } - glog.Infof("Loaded %d accounts", len(u.Accounts)) - - return &Cache{ - shared: s, - accounts: &accountService{s}, - config: &configService{s}, - }, nil -} - -// This empty function exists so the Cache struct implements the Cache interface defined in cache/legacy.go -func (c *Cache) Close() error { - return nil -} - -func (c *Cache) Accounts() cache.AccountsService { - return c.accounts -} -func (c *Cache) Config() cache.ConfigService { - return c.config -} - -// AccountService handles the account information -type accountService struct { - shared *shared -} - -// Get will return Account from memory if it exists -func (s *accountService) Get(id string) (*cache.Account, error) { - if _, ok := s.shared.Accounts[id]; !ok { - return nil, fmt.Errorf("Not found") - } - return &cache.Account{ - ID: id, - }, nil -} - -// ConfigService not supported, always returns an error -type configService struct { - shared *shared -} - -// Get will return config from memory if it exists -func (s *configService) Get(id string) (string, error) { - cfg, ok := s.shared.Configs[id] - if !ok { - return "", fmt.Errorf("Not found") - } - return cfg, nil -} - -// Set not supported, always returns an error -func (s *configService) Set(id, value string) error { - return fmt.Errorf("Not supported") -} diff --git a/cache/filecache/filecache_test.go b/cache/filecache/filecache_test.go deleted file mode 100644 index 80a72803bbe..00000000000 --- a/cache/filecache/filecache_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package filecache - -import ( - "io/ioutil" - "os" - "testing" - - yaml "gopkg.in/yaml.v2" -) - -func TestFileCache(t *testing.T) { - fcf := fileCacheFile{ - Accounts: []string{"account1", "account2", "account3"}, - Configs: []fileConfig{ - { - ID: "one", - Config: "config1", - }, { - ID: "two", - Config: "config2", - }, { - ID: "three", - Config: "config3", - }, - }, - } - - bytes, err := yaml.Marshal(&fcf) - if err != nil { - t.Fatal(err) - } - - tmpfile, err := ioutil.TempFile("", "filecache") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpfile.Name()) - - if _, err := tmpfile.Write(bytes); err != nil { - t.Fatal(err) - } - - if err := tmpfile.Close(); err != nil { - t.Fatal(err) - } - - dataCache, err := New(tmpfile.Name()) - if err != nil { - t.Fatal(err) - } - - a, err := dataCache.Accounts().Get("account1") - if err != nil { - t.Fatal(err) - } - - if a.ID != "account1" { - t.Error("fetched invalid account") - } - - a, err = dataCache.Accounts().Get("abc123") - if err == nil { - t.Error("account should not exist in cache") - } - - c, err := dataCache.Config().Get("one") - if err != nil { - t.Fatal(err) - } - - if c != "config1" { - t.Error("fetched invalid config") - } - - c, err = dataCache.Config().Get("abc123") - if err == nil { - t.Error("config should not exist in cache") - } -} diff --git a/cache/legacy.go b/cache/legacy.go deleted file mode 100644 index 19c5ae5a4fe..00000000000 --- a/cache/legacy.go +++ /dev/null @@ -1,33 +0,0 @@ -package cache - -type Domain struct { - Domain string `json:"domain"` -} - -type App struct { - Bundle string `json:"bundle"` -} - -type Account struct { - ID string `json:"id"` - PriceGranularity string `json:"price_granularity"` -} - -type Configuration struct { - Type string `json:"type"` // required -} - -type Cache interface { - Close() error - Accounts() AccountsService - Config() ConfigService -} - -type AccountsService interface { - Get(string) (*Account, error) -} - -type ConfigService interface { - Get(string) (string, error) - Set(string, string) error -} diff --git a/cache/postgrescache/postgrescache.go b/cache/postgrescache/postgrescache.go deleted file mode 100644 index 2333e08269e..00000000000 --- a/cache/postgrescache/postgrescache.go +++ /dev/null @@ -1,139 +0,0 @@ -package postgrescache - -import ( - "bytes" - "context" - "database/sql" - "encoding/gob" - "time" - - "github.com/prebid/prebid-server/stored_requests" - - "github.com/coocood/freecache" - "github.com/lib/pq" - "github.com/prebid/prebid-server/cache" -) - -type CacheConfig struct { - TTL int - Size int -} - -// shared configuration that get used by all of the services -type shared struct { - db *sql.DB - lru *freecache.Cache - ttlSeconds int -} - -// Cache postgres -type Cache struct { - shared *shared - accounts *accountService - config *configService -} - -// New creates new postgres.Cache -func New(db *sql.DB, cfg CacheConfig) *Cache { - shared := &shared{ - db: db, - lru: freecache.NewCache(cfg.Size), - ttlSeconds: cfg.TTL, - } - return &Cache{ - shared: shared, - accounts: &accountService{shared: shared}, - config: &configService{shared: shared}, - } -} - -func (c *Cache) Accounts() cache.AccountsService { - return c.accounts -} -func (c *Cache) Config() cache.ConfigService { - return c.config -} - -func (c *Cache) Close() error { - return c.shared.db.Close() -} - -// AccountService handles the account information -type accountService struct { - shared *shared -} - -// Get echos back the account -func (s *accountService) Get(key string) (*cache.Account, error) { - var account cache.Account - - b, err := s.shared.lru.Get([]byte(key)) - if err == nil { - return decodeAccount(b), nil - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50)*time.Millisecond) - defer cancel() - var id string - var priceGranularity sql.NullString - if err := s.shared.db.QueryRowContext(ctx, "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1", key).Scan(&id, &priceGranularity); err != nil { - /* TODO -- We should store failed attempts in the LRU as well to stop from hitting to DB */ - return nil, err - } - - account.ID = id - if priceGranularity.Valid { - account.PriceGranularity = priceGranularity.String - } - - buf := bytes.Buffer{} - if err := gob.NewEncoder(&buf).Encode(&account); err != nil { - panic(err) - } - - s.shared.lru.Set([]byte(key), buf.Bytes(), s.shared.ttlSeconds) - return &account, nil -} - -func decodeAccount(b []byte) *cache.Account { - var account cache.Account - buf := bytes.NewReader(b) - if err := gob.NewDecoder(buf).Decode(&account); err != nil { - panic(err) - } - return &account -} - -// ConfigService -type configService struct { - shared *shared -} - -func (s *configService) Set(id, value string) error { - return nil -} - -func (s *configService) Get(key string) (string, error) { - if b, err := s.shared.lru.Get([]byte(key)); err == nil { - return string(b), nil - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50)*time.Millisecond) - defer cancel() - var config string - if err := s.shared.db.QueryRowContext(ctx, "SELECT config FROM s2sconfig_config where uuid = $1 LIMIT 1", key).Scan(&config); err != nil { - // TODO -- We should store failed attempts in the LRU as well to stop from hitting to DB - - // If the user didn't give us a UUID, the query fails with this error. Wrap it so that we don't - // pollute the app logs with bad user input. - if pqErr, ok := err.(*pq.Error); ok && string(pqErr.Code) == "22P02" { - err = &stored_requests.NotFoundError{ - ID: key, - DataType: "Legacy Config", - } - } - return "", err - } - s.shared.lru.Set([]byte(key), []byte(config), s.shared.ttlSeconds) - return config, nil -} diff --git a/cache/postgrescache/postgrescache_test.go b/cache/postgrescache/postgrescache_test.go deleted file mode 100644 index bab96f1a11e..00000000000 --- a/cache/postgrescache/postgrescache_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package postgrescache - -import ( - "database/sql" - "testing" - - "github.com/coocood/freecache" - "github.com/erikstmartin/go-testdb" -) - -type StubCache struct { - shared *shared - accounts *accountService - config *configService -} - -// New creates new postgres.Cache -func StubNew(cfg CacheConfig) *Cache { - shared := stubnewShared(cfg) - return &Cache{ - shared: shared, - accounts: &accountService{shared: shared}, - config: &configService{shared: shared}, - } -} - -func stubnewShared(conf CacheConfig) *shared { - db, _ := sql.Open("testdb", "") - - s := &shared{ - db: db, - lru: freecache.NewCache(conf.Size), - ttlSeconds: 0, - } - return s -} - -func TestPostgresDbPriceGranularity(t *testing.T) { - defer testdb.Reset() - - sql := "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1" - columns := []string{"uuid", "price_granularity"} - result := ` - bdc928ef-f725-4688-8171-c104cc715bdf,med - ` - testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result)) - - conf := CacheConfig{ - TTL: 3434, - Size: 100, - } - dataCache := StubNew(conf) - - account, err := dataCache.Accounts().Get("bdc928ef-f725-4688-8171-c104cc715bdf") - if err != nil { - t.Fatalf("test postgres db errored: %v", err) - } - - if account.ID != "bdc928ef-f725-4688-8171-c104cc715bdf" { - t.Error("Expected bdc928ef-f725-4688-8171-c104cc715bdf") - } - if account.PriceGranularity != "med" { - t.Error("Expected med") - } -} - -func TestPostgresDbNullPriceGranularity(t *testing.T) { - defer testdb.Reset() - - sql := "SELECT uuid, price_granularity FROM accounts_account where uuid = $1 LIMIT 1" - columns := []string{"uuid", "price_granularity"} - result := ` - bdc928ef-f725-4688-8171-c104cc715bdf - ` - testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result)) - - conf := CacheConfig{ - TTL: 3434, - Size: 100, - } - dataCache := StubNew(conf) - - account, err := dataCache.Accounts().Get("bdc928ef-f725-4688-8171-c104cc715bdf") - if err != nil { - t.Fatalf("test postgres db errored: %v", err) - } - - if account.ID != "bdc928ef-f725-4688-8171-c104cc715bdf" { - t.Error("Expected bdc928ef-f725-4688-8171-c104cc715bdf") - } - if account.PriceGranularity != "" { - t.Error("Expected null string") - } -} diff --git a/config/config.go b/config/config.go index 49a9d2dbcdf..128b005af76 100644 --- a/config/config.go +++ b/config/config.go @@ -33,7 +33,6 @@ type Configuration struct { RecaptchaSecret string `mapstructure:"recaptcha_secret"` HostCookie HostCookie `mapstructure:"host_cookie"` Metrics Metrics `mapstructure:"metrics"` - DataCache DataCache `mapstructure:"datacache"` StoredRequests StoredRequests `mapstructure:"stored_requests"` StoredRequestsAMP StoredRequests `mapstructure:"stored_amp_req"` CategoryMapping StoredRequests `mapstructure:"category_mapping"` @@ -85,10 +84,6 @@ type Configuration struct { GenerateBidID bool `mapstructure:"generate_bid_id"` // GenerateRequestID overrides the bidrequest.id in an AMP Request or an App Stored Request with a generated UUID if set to true. The default is false. GenerateRequestID bool `mapstructure:"generate_request_id"` - - // EnableLegacyAuction specifies if the original /auction endpoint with a custom PBS data model is allowed - // by the host. - EnableLegacyAuction bool `mapstructure:"enable_legacy_auction"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -432,13 +427,6 @@ func (m *PrometheusMetrics) Timeout() time.Duration { return time.Duration(m.TimeoutMillisRaw) * time.Millisecond } -type DataCache struct { - Type string `mapstructure:"type"` - Filename string `mapstructure:"filename"` - CacheSize int `mapstructure:"cache_size"` - TTLSeconds int `mapstructure:"ttl_seconds"` -} - // ExternalCache configures the externally accessible cache url. type ExternalCache struct { Scheme string `mapstructure:"scheme"` @@ -691,10 +679,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("metrics.prometheus.namespace", "") v.SetDefault("metrics.prometheus.subsystem", "") v.SetDefault("metrics.prometheus.timeout_ms", 10000) - v.SetDefault("datacache.type", "dummy") - v.SetDefault("datacache.filename", "") - v.SetDefault("datacache.cache_size", 0) - v.SetDefault("datacache.ttl_seconds", 0) v.SetDefault("category_mapping.filesystem.enabled", true) v.SetDefault("category_mapping.filesystem.directorypath", "./static/category-mapping") v.SetDefault("category_mapping.http.endpoint", "") @@ -975,7 +959,6 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("auto_gen_source_tid", true) v.SetDefault("generate_bid_id", false) v.SetDefault("generate_request_id", false) - v.SetDefault("enable_legacy_auction", false) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") diff --git a/config/config_test.go b/config/config_test.go index a2c5c952d84..9e9c70375e4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -130,7 +130,6 @@ func TestDefaults(t *testing.T) { cmpInts(t, "max_request_size", int(cfg.MaxRequestSize), 1024*256) cmpInts(t, "host_cookie.ttl_days", int(cfg.HostCookie.TTL), 90) cmpInts(t, "host_cookie.max_cookie_size_bytes", cfg.HostCookie.MaxCookieSizeBytes, 0) - cmpStrings(t, "datacache.type", cfg.DataCache.Type, "dummy") cmpStrings(t, "adapters.pubmatic.endpoint", cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint, "https://hbopenbid.pubmatic.com/translator?source=prebid-server") cmpInts(t, "currency_converter.fetch_interval_seconds", cfg.CurrencyConverter.FetchIntervalSeconds, 1800) cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") @@ -144,7 +143,6 @@ func TestDefaults(t *testing.T) { 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) - cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, false) //Assert purpose VendorExceptionMap hash tables were built correctly expectedTCF2 := TCF2{ @@ -327,11 +325,6 @@ metrics: account_adapter_details: true adapter_connections_metrics: true adapter_gdpr_request_blocked: true -datacache: - type: postgres - filename: /usr/db/db.db - cache_size: 10000000 - ttl_seconds: 3600 adapters: appnexus: endpoint: http://ib.adnxs.com/some/endpoint @@ -365,7 +358,6 @@ request_validation: ipv4_private_networks: ["1.1.1.0/24"] ipv6_private_networks: ["1111::/16", "2222::/16"] generate_bid_id: true -enable_legacy_auction: true `) var adapterExtraInfoConfig = []byte(` @@ -566,10 +558,6 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "metrics.influxdb.username", cfg.Metrics.Influxdb.Username, "admin") cmpStrings(t, "metrics.influxdb.password", cfg.Metrics.Influxdb.Password, "admin1324") cmpInts(t, "metrics.influxdb.metric_send_interval", cfg.Metrics.Influxdb.MetricSendInterval, 30) - cmpStrings(t, "datacache.type", cfg.DataCache.Type, "postgres") - cmpStrings(t, "datacache.filename", cfg.DataCache.Filename, "/usr/db/db.db") - cmpInts(t, "datacache.cache_size", cfg.DataCache.CacheSize, 10000000) - cmpInts(t, "datacache.ttl_seconds", cfg.DataCache.TTLSeconds, 3600) cmpStrings(t, "", cfg.CacheURL.GetBaseURL(), "http://prebidcache.net") cmpStrings(t, "", cfg.GetCachedAssetURL("a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11"), "http://prebidcache.net/cache?uuid=a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11") cmpStrings(t, "adapters.appnexus.endpoint", cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, "http://ib.adnxs.com/some/endpoint") @@ -600,7 +588,6 @@ func TestFullConfig(t *testing.T) { 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, "") - cmpBools(t, "enable_legacy_auction", cfg.EnableLegacyAuction, true) } func TestUnmarshalAdapterExtraInfo(t *testing.T) { diff --git a/config/stored_requests.go b/config/stored_requests.go index ee78179eb65..e752e9e4d9d 100644 --- a/config/stored_requests.go +++ b/config/stored_requests.go @@ -133,7 +133,6 @@ func resolvedStoredRequestsConfig(cfg *Configuration) { cfg.StoredVideo.dataType = VideoDataType cfg.CategoryMapping.dataType = CategoryDataType cfg.Accounts.dataType = AccountDataType - return } func (cfg *StoredRequests) validate(errs []error) []error { diff --git a/config/structlog.go b/config/structlog.go index a91e5ab857e..911f475717d 100644 --- a/config/structlog.go +++ b/config/structlog.go @@ -12,7 +12,7 @@ import ( type logMsg func(string, ...interface{}) var mapregex = regexp.MustCompile(`mapstructure:"([^"]+)"`) -var blacklistregexp = []*regexp.Regexp{ +var blocklistregexp = []*regexp.Regexp{ regexp.MustCompile("password"), } @@ -84,7 +84,7 @@ func fieldNameByTag(f reflect.StructField) string { } func allowedName(name string) bool { - for _, r := range blacklistregexp { + for _, r := range blocklistregexp { if r.MatchString(name) { return false } diff --git a/endpoints/auction.go b/endpoints/auction.go deleted file mode 100644 index 10d2ced6c37..00000000000 --- a/endpoints/auction.go +++ /dev/null @@ -1,513 +0,0 @@ -package endpoints - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "runtime/debug" - "sort" - "strconv" - "time" - - "github.com/golang/glog" - "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/cache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/exchange" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - pbc "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/prebid/prebid-server/privacy" - gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/prebid/prebid-server/usersync" -) - -var allSyncTypes []usersync.SyncType = []usersync.SyncType{usersync.SyncTypeIFrame, usersync.SyncTypeRedirect} - -type bidResult struct { - bidder *pbs.PBSBidder - bidList pbs.PBSBidSlice -} - -const defaultPriceGranularity = "med" - -func min(x, y int) int { - if x < y { - return x - } - return y -} - -func writeAuctionError(w http.ResponseWriter, s string, err error) { - var resp pbs.PBSResponse - if err != nil { - resp.Status = fmt.Sprintf("%s: %v", s, err) - } else { - resp.Status = s - } - b, err := json.Marshal(&resp) - if err != nil { - glog.Errorf("Failed to marshal auction error JSON: %s", err) - } else { - w.Write(b) - } -} - -type auction struct { - cfg *config.Configuration - syncersByBidder map[string]usersync.Syncer - gdprPerms gdpr.Permissions - metricsEngine metrics.MetricsEngine - dataCache cache.Cache - exchanges map[string]adapters.Adapter -} - -func Auction(cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, gdprPerms gdpr.Permissions, metricsEngine metrics.MetricsEngine, dataCache cache.Cache, exchanges map[string]adapters.Adapter) httprouter.Handle { - a := &auction{ - cfg: cfg, - syncersByBidder: syncersByBidder, - gdprPerms: gdprPerms, - metricsEngine: metricsEngine, - dataCache: dataCache, - exchanges: exchanges, - } - return a.auction -} - -func (a *auction) auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - w.Header().Add("Content-Type", "application/json") - var labels = getDefaultLabels(r) - req, err := pbs.ParsePBSRequest(r, &a.cfg.AuctionTimeouts, a.dataCache, &(a.cfg.HostCookie)) - - defer a.recordMetrics(req, labels) - - if err != nil { - if glog.V(2) { - glog.Infof("Failed to parse /auction request: %v", err) - } - writeAuctionError(w, "Error parsing request", err) - labels.RequestStatus = metrics.RequestStatusBadInput - return - } - status := "OK" - setLabelSource(&labels, req, &status) - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(req.TimeoutMillis)) - defer cancel() - account, err := a.dataCache.Accounts().Get(req.AccountID) - if err != nil { - if glog.V(2) { - glog.Infof("Invalid account id: %v", err) - } - writeAuctionError(w, "Unknown account id", fmt.Errorf("Unknown account")) - labels.RequestStatus = metrics.RequestStatusBadInput - return - } - labels.PubID = req.AccountID - resp := pbs.PBSResponse{ - Status: status, - TID: req.Tid, - BidderStatus: req.Bidders, - } - ch := make(chan bidResult) - sentBids := 0 - for _, bidder := range req.Bidders { - if ex, ok := a.exchanges[bidder.BidderCode]; ok { - // Make sure we have an independent label struct for each bidder. We don't want to run into issues with the goroutine below. - blabels := metrics.AdapterLabels{ - Source: labels.Source, - RType: labels.RType, - Adapter: openrtb_ext.BidderName(bidder.BidderCode), - PubID: labels.PubID, - CookieFlag: labels.CookieFlag, - AdapterBids: metrics.AdapterBidPresent, - } - if skip := a.processUserSync(req, bidder, blabels, ex, &ctx); skip == true { - continue - } - sentBids++ - bidderRunner := a.recoverSafely(func(bidder *pbs.PBSBidder, aLabels metrics.AdapterLabels) { - - start := time.Now() - bidList, err := ex.Call(ctx, req, bidder) - a.metricsEngine.RecordAdapterTime(aLabels, time.Since(start)) - bidder.ResponseTime = int(time.Since(start) / time.Millisecond) - processBidResult(bidList, bidder, &aLabels, a.metricsEngine, err) - - ch <- bidResult{ - bidder: bidder, - bidList: bidList, - // Bidder done, record bidder metrics - } - a.metricsEngine.RecordAdapterRequest(aLabels) - }) - - go bidderRunner(bidder, blabels) - - } else if bidder.BidderCode == "lifestreet" { - bidder.Error = "Bidder is no longer available" - } else { - bidder.Error = "Unsupported bidder" - } - } - for i := 0; i < sentBids; i++ { - result := <-ch - for _, bid := range result.bidList { - resp.Bids = append(resp.Bids, bid) - } - } - if err := cacheAccordingToMarkup(req, &resp, ctx, a, &labels); err != nil { - writeAuctionError(w, "Prebid cache failed", err) - labels.RequestStatus = metrics.RequestStatusErr - return - } - if req.SortBids == 1 { - sortBidsAddKeywordsMobile(resp.Bids, req, account.PriceGranularity) - } - if glog.V(2) { - glog.Infof("Request for %d ad units on url %s by account %s got %d bids", len(req.AdUnits), req.Url, req.AccountID, len(resp.Bids)) - } - enc := json.NewEncoder(w) - enc.SetEscapeHTML(false) - enc.Encode(resp) -} - -func (a *auction) recoverSafely(inner func(*pbs.PBSBidder, metrics.AdapterLabels)) func(*pbs.PBSBidder, metrics.AdapterLabels) { - return func(bidder *pbs.PBSBidder, labels metrics.AdapterLabels) { - defer func() { - if r := recover(); r != nil { - if bidder == nil { - glog.Errorf("Legacy auction recovered panic: %v. Stack trace is: %v", r, string(debug.Stack())) - } else { - glog.Errorf("Legacy auction recovered panic from Bidder %s: %v. Stack trace is: %v", bidder.BidderCode, r, string(debug.Stack())) - } - a.metricsEngine.RecordAdapterPanic(labels) - } - }() - inner(bidder, labels) - } -} - -func (a *auction) shouldUsersync(ctx context.Context, bidder openrtb_ext.BidderName, gdprPrivacyPolicy gdprPrivacy.Policy) bool { - gdprSignal := gdpr.SignalAmbiguous - if signal, err := gdpr.SignalParse(gdprPrivacyPolicy.Signal); err != nil { - gdprSignal = signal - } - - if canSync, err := a.gdprPerms.HostCookiesAllowed(ctx, gdprSignal, gdprPrivacyPolicy.Consent); err != nil || !canSync { - return false - } - canSync, err := a.gdprPerms.BidderSyncAllowed(ctx, bidder, gdprSignal, gdprPrivacyPolicy.Consent) - return canSync && err == nil -} - -// cache video bids only for Web -func cacheVideoOnly(bids pbs.PBSBidSlice, ctx context.Context, deps *auction, labels *metrics.Labels) error { - var cobjs []*pbc.CacheObject - for _, bid := range bids { - if bid.CreativeMediaType == "video" { - cobjs = append(cobjs, &pbc.CacheObject{ - Value: bid.Adm, - IsVideo: true, - }) - } - } - err := pbc.Put(ctx, cobjs) - if err != nil { - return err - } - videoIndex := 0 - for _, bid := range bids { - if bid.CreativeMediaType == "video" { - bid.CacheID = cobjs[videoIndex].UUID - bid.CacheURL = deps.cfg.GetCachedAssetURL(bid.CacheID) - bid.NURL = "" - bid.Adm = "" - videoIndex++ - } - } - return nil -} - -// checkForValidBidSize goes through list of bids & find those which are banner mediaType and with height or width not defined -// determine the num of ad unit sizes that were used in corresponding bid request -// if num_adunit_sizes == 1, assign the height and/or width to bid's height/width -// if num_adunit_sizes > 1, reject the bid (remove from list) and return an error -// return updated bid list object for next steps in auction -func checkForValidBidSize(bids pbs.PBSBidSlice, bidder *pbs.PBSBidder) pbs.PBSBidSlice { - finalValidBids := make([]*pbs.PBSBid, len(bids)) - finalBidCounter := 0 -bidLoop: - for _, bid := range bids { - if isUndimensionedBanner(bid) { - for _, adunit := range bidder.AdUnits { - if copyBannerDimensions(&adunit, bid, finalValidBids, &finalBidCounter) { - continue bidLoop - } - } - } else { - finalValidBids[finalBidCounter] = bid - finalBidCounter = finalBidCounter + 1 - } - } - return finalValidBids[:finalBidCounter] -} - -func isUndimensionedBanner(bid *pbs.PBSBid) bool { - return bid.CreativeMediaType == "banner" && (bid.Height == 0 || bid.Width == 0) -} - -func copyBannerDimensions(adunit *pbs.PBSAdUnit, bid *pbs.PBSBid, finalValidBids []*pbs.PBSBid, finalBidCounter *int) bool { - var bidIDEqualsCode bool = false - - if adunit.BidID == bid.BidID && adunit.Code == bid.AdUnitCode && adunit.Sizes != nil { - if len(adunit.Sizes) == 1 { - bid.Width, bid.Height = adunit.Sizes[0].W, adunit.Sizes[0].H - finalValidBids[*finalBidCounter] = bid - *finalBidCounter += 1 - } else if len(adunit.Sizes) > 1 { - glog.Warningf("Bid was rejected for bidder %s because no size was defined", bid.BidderCode) - } - bidIDEqualsCode = true - } - - return bidIDEqualsCode -} - -// sortBidsAddKeywordsMobile sorts the bids and adds ad server targeting keywords to each bid. -// The bids are sorted by cpm to find the highest bid. -// The ad server targeting keywords are added to all bids, with specific keywords for the highest bid. -func sortBidsAddKeywordsMobile(bids pbs.PBSBidSlice, pbs_req *pbs.PBSRequest, priceGranularitySetting string) { - if priceGranularitySetting == "" { - priceGranularitySetting = defaultPriceGranularity - } - - // record bids by ad unit code for sorting - code_bids := make(map[string]pbs.PBSBidSlice, len(bids)) - for _, bid := range bids { - code_bids[bid.AdUnitCode] = append(code_bids[bid.AdUnitCode], bid) - } - - // loop through ad units to find top bid - for _, unit := range pbs_req.AdUnits { - bar := code_bids[unit.Code] - - if len(bar) == 0 { - if glog.V(3) { - glog.Infof("No bids for ad unit '%s'", unit.Code) - } - continue - } - sort.Sort(bar) - - // after sorting we need to add the ad targeting keywords - for i, bid := range bar { - // We should eventually check for the error and do something. - roundedCpm := exchange.GetPriceBucket(bid.Price, openrtb_ext.PriceGranularityFromString(priceGranularitySetting)) - - hbSize := "" - if bid.Width != 0 && bid.Height != 0 { - width := strconv.FormatInt(bid.Width, 10) - height := strconv.FormatInt(bid.Height, 10) - hbSize = width + "x" + height - } - - hbPbBidderKey := string(openrtb_ext.HbpbConstantKey) + "_" + bid.BidderCode - hbBidderBidderKey := string(openrtb_ext.HbBidderConstantKey) + "_" + bid.BidderCode - hbCacheIDBidderKey := string(openrtb_ext.HbCacheKey) + "_" + bid.BidderCode - hbDealIDBidderKey := string(openrtb_ext.HbDealIDConstantKey) + "_" + bid.BidderCode - hbSizeBidderKey := string(openrtb_ext.HbSizeConstantKey) + "_" + bid.BidderCode - if pbs_req.MaxKeyLength != 0 { - hbPbBidderKey = hbPbBidderKey[:min(len(hbPbBidderKey), int(pbs_req.MaxKeyLength))] - hbBidderBidderKey = hbBidderBidderKey[:min(len(hbBidderBidderKey), int(pbs_req.MaxKeyLength))] - hbCacheIDBidderKey = hbCacheIDBidderKey[:min(len(hbCacheIDBidderKey), int(pbs_req.MaxKeyLength))] - hbDealIDBidderKey = hbDealIDBidderKey[:min(len(hbDealIDBidderKey), int(pbs_req.MaxKeyLength))] - hbSizeBidderKey = hbSizeBidderKey[:min(len(hbSizeBidderKey), int(pbs_req.MaxKeyLength))] - } - - // fixes #288 where map was being overwritten instead of updated - if bid.AdServerTargeting == nil { - bid.AdServerTargeting = make(map[string]string) - } - kvs := bid.AdServerTargeting - - kvs[hbPbBidderKey] = roundedCpm - kvs[hbBidderBidderKey] = bid.BidderCode - kvs[hbCacheIDBidderKey] = bid.CacheID - - if hbSize != "" { - kvs[hbSizeBidderKey] = hbSize - } - if bid.DealId != "" { - kvs[hbDealIDBidderKey] = bid.DealId - } - // For the top bid, we want to add the following additional keys - if i == 0 { - kvs[string(openrtb_ext.HbpbConstantKey)] = roundedCpm - kvs[string(openrtb_ext.HbBidderConstantKey)] = bid.BidderCode - kvs[string(openrtb_ext.HbCacheKey)] = bid.CacheID - if bid.DealId != "" { - kvs[string(openrtb_ext.HbDealIDConstantKey)] = bid.DealId - } - if hbSize != "" { - kvs[string(openrtb_ext.HbSizeConstantKey)] = hbSize - } - } - } - } -} - -func getDefaultLabels(r *http.Request) metrics.Labels { - return metrics.Labels{ - Source: metrics.DemandUnknown, - RType: metrics.ReqTypeLegacy, - PubID: "", - CookieFlag: metrics.CookieFlagUnknown, - RequestStatus: metrics.RequestStatusOK, - } -} - -func setLabelSource(labels *metrics.Labels, req *pbs.PBSRequest, status *string) { - if req.App != nil { - labels.Source = metrics.DemandApp - } else { - labels.Source = metrics.DemandWeb - if req.Cookie.HasAnyLiveSyncs() { - labels.CookieFlag = metrics.CookieFlagYes - } else { - labels.CookieFlag = metrics.CookieFlagNo - *status = "no_cookie" - } - } -} - -func cacheAccordingToMarkup(req *pbs.PBSRequest, resp *pbs.PBSResponse, ctx context.Context, a *auction, labels *metrics.Labels) error { - if req.CacheMarkup == 1 { - cobjs := make([]*pbc.CacheObject, len(resp.Bids)) - for i, bid := range resp.Bids { - if bid.CreativeMediaType == "video" { - cobjs[i] = &pbc.CacheObject{ - Value: bid.Adm, - IsVideo: true, - } - } else { - cobjs[i] = &pbc.CacheObject{ - Value: &pbc.BidCache{ - Adm: bid.Adm, - NURL: bid.NURL, - Width: bid.Width, - Height: bid.Height, - }, - IsVideo: false, - } - } - } - if err := pbc.Put(ctx, cobjs); err != nil { - return err - } - for i, bid := range resp.Bids { - bid.CacheID = cobjs[i].UUID - bid.CacheURL = a.cfg.GetCachedAssetURL(bid.CacheID) - bid.NURL = "" - bid.Adm = "" - } - } else if req.CacheMarkup == 2 { - return cacheVideoOnly(resp.Bids, ctx, a, labels) - } - return nil -} - -func processBidResult(bidList pbs.PBSBidSlice, bidder *pbs.PBSBidder, aLabels *metrics.AdapterLabels, metricsEngine metrics.MetricsEngine, err error) { - if err != nil { - var s struct{} - if err == context.DeadlineExceeded { - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorTimeout: s} - bidder.Error = "Timed out" - } else if err != context.Canceled { - bidder.Error = err.Error() - switch err.(type) { - case *errortypes.BadInput: - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorBadInput: s} - case *errortypes.BadServerResponse: - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorBadServerResponse: s} - default: - glog.Warningf("Error from bidder %v. Ignoring all bids: %v", bidder.BidderCode, err) - aLabels.AdapterErrors = map[metrics.AdapterError]struct{}{metrics.AdapterErrorUnknown: s} - } - } - } else if bidList != nil { - bidList = checkForValidBidSize(bidList, bidder) - bidder.NumBids = len(bidList) - for _, bid := range bidList { - var cpm = float64(bid.Price * 1000) - metricsEngine.RecordAdapterPrice(*aLabels, cpm) - switch bid.CreativeMediaType { - case "banner": - metricsEngine.RecordAdapterBidReceived(*aLabels, openrtb_ext.BidTypeBanner, bid.Adm != "") - case "video": - metricsEngine.RecordAdapterBidReceived(*aLabels, openrtb_ext.BidTypeVideo, bid.Adm != "") - } - bid.ResponseTime = bidder.ResponseTime - } - } else { - bidder.NoBid = true - aLabels.AdapterBids = metrics.AdapterBidNone - } -} - -func (a *auction) recordMetrics(req *pbs.PBSRequest, labels metrics.Labels) { - a.metricsEngine.RecordRequest(labels) - if req == nil { - a.metricsEngine.RecordLegacyImps(labels, 0) - return - } - a.metricsEngine.RecordLegacyImps(labels, len(req.AdUnits)) - a.metricsEngine.RecordRequestTime(labels, time.Since(req.Start)) -} - -func (a *auction) processUserSync(req *pbs.PBSRequest, bidder *pbs.PBSBidder, blabels metrics.AdapterLabels, ex adapters.Adapter, ctx *context.Context) bool { - var skip bool = false - if req.App != nil { - return skip - } - // If exchanges[bidderCode] exists, then a.syncers[bidderCode] exists *except for districtm*. - // OpenRTB handles aliases differently, so this hack will keep legacy code working. For all other - // bidderCodes, a.syncers[bidderCode] will exist if exchanges[bidderCode] also does. - // This is guaranteed by the TestSyncers unit test inside usersync/usersync_test.go, which compares these maps to the (source of truth) openrtb_ext.BidderMap: - syncerCode := bidder.BidderCode - if syncerCode == "districtm" { - syncerCode = "appnexus" - } - syncer := a.syncersByBidder[syncerCode] - uid, _, _ := req.Cookie.GetUID(syncer.Key()) - if uid == "" { - bidder.NoCookie = true - privacyPolicies := privacy.Policies{ - GDPR: gdprPrivacy.Policy{ - Signal: req.ParseGDPR(), - Consent: req.ParseConsent(), - }, - } - if a.shouldUsersync(*ctx, openrtb_ext.BidderName(syncerCode), privacyPolicies.GDPR) { - sync, err := syncer.GetSync(allSyncTypes, privacyPolicies) - if err == nil { - bidder.UsersyncInfo = &pbs.UsersyncInfo{ - URL: sync.URL, - Type: string(sync.Type), - SupportCORS: sync.SupportCORS, - } - } else { - glog.Errorf("Failed to get usersync info for %s: %v", syncerCode, err) - } - } - blabels.CookieFlag = metrics.CookieFlagNo - if ex.SkipNoCookies() { - skip = true - } - } - return skip -} diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go deleted file mode 100644 index 1a30e025faa..00000000000 --- a/endpoints/auction_test.go +++ /dev/null @@ -1,654 +0,0 @@ -package endpoints - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/gdpr" - "github.com/prebid/prebid-server/metrics" - metricsConf "github.com/prebid/prebid-server/metrics/config" - "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/prebid_cache_client" - gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/prebid/prebid-server/usersync" - "github.com/spf13/viper" - - "github.com/stretchr/testify/assert" -) - -func TestSortBidsAndAddKeywordsForMobile(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":"F", - "buyeruid":"test_buyeruid", - "yob":2000, - "id":"testid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"test_adunitcode" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.1" - }, - "sdk":{ - "version":"0.0.1", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := pbs.ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Errorf("Unexpected error on parsing %v", err) - } - - bids := make(pbs.PBSBidSlice, 0) - - fb_bid := pbs.PBSBid{ - BidID: "test_bidid", - AdUnitCode: "test_adunitcode", - BidderCode: "audienceNetwork", - Price: 2.00, - Adm: "test_adm", - Width: 300, - Height: 250, - CacheID: "test_cache_id1", - DealId: "2345", - } - bids = append(bids, &fb_bid) - an_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "appnexus", - Price: 1.00, - Adm: "test_adm", - Width: 320, - Height: 50, - CacheID: "test_cache_id2", - DealId: "1234", - } - bids = append(bids, &an_bid) - rb_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "rubicon", - Price: 1.00, - Adm: "test_adm", - Width: 300, - Height: 250, - CacheID: "test_cache_id2", - DealId: "7890", - } - rb_bid.AdServerTargeting = map[string]string{ - "rpfl_1001": "15_tier0100", - } - bids = append(bids, &rb_bid) - nosize_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "nosizebidder", - Price: 1.00, - Adm: "test_adm", - CacheID: "test_cache_id2", - } - bids = append(bids, &nosize_bid) - nodeal_bid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode", - BidderCode: "nodeal", - Price: 1.00, - Adm: "test_adm", - CacheID: "test_cache_id2", - } - bids = append(bids, &nodeal_bid) - pbs_resp := pbs.PBSResponse{ - Bids: bids, - } - sortBidsAddKeywordsMobile(pbs_resp.Bids, pbs_req, "") - - for _, bid := range bids { - if bid.AdServerTargeting == nil { - t.Error("Ad server targeting should not be nil") - } - if bid.BidderCode == "audienceNetwork" { - if bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)] != "300x250" { - t.Error(string(openrtb_ext.HbSizeConstantKey) + " key was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)] != "2.00" { - t.Error(string(openrtb_ext.HbpbConstantKey)+" key was not parsed correctly ", bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)]) - } - - if bid.AdServerTargeting[string(openrtb_ext.HbCacheKey)] != "test_cache_id1" { - t.Error(string(openrtb_ext.HbCacheKey) + " key was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbBidderConstantKey)] != "audienceNetwork" { - t.Error(string(openrtb_ext.HbBidderConstantKey) + " key was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)] != "2345" { - t.Error(string(openrtb_ext.HbDealIDConstantKey) + " key was not parsed correctly ") - } - } - if bid.BidderCode == "appnexus" { - if bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)+"_appnexus"] != "320x50" { - t.Error(string(openrtb_ext.HbSizeConstantKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbCacheKey)+"_appnexus"] != "test_cache_id2" { - t.Error(string(openrtb_ext.HbCacheKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbBidderConstantKey)+"_appnexus"] != "appnexus" { - t.Error(string(openrtb_ext.HbBidderConstantKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)+"_appnexus"] != "1.00" { - t.Error(string(openrtb_ext.HbpbConstantKey) + " key for appnexus bidder was not parsed correctly") - } - if bid.AdServerTargeting[string(openrtb_ext.HbpbConstantKey)] != "" { - t.Error(string(openrtb_ext.HbpbConstantKey) + " key was parsed for two bidders") - } - if bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_appnexus"] != "1234" { - t.Errorf(string(openrtb_ext.HbDealIDConstantKey)+"_appnexus was not parsed correctly %v", bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_appnexus"]) - } - } - if bid.BidderCode == string(openrtb_ext.BidderRubicon) { - if bid.AdServerTargeting["rpfl_1001"] != "15_tier0100" { - t.Error("custom ad_server_targeting KVPs from adapter were not preserved") - } - } - if bid.BidderCode == "nosizebidder" { - if _, exists := bid.AdServerTargeting[string(openrtb_ext.HbSizeConstantKey)+"_nosizebidder"]; exists { - t.Error(string(openrtb_ext.HbSizeConstantKey)+" key for nosize bidder was not parsed correctly", bid.AdServerTargeting) - } - } - if bid.BidderCode == "nodeal" { - if _, exists := bid.AdServerTargeting[string(openrtb_ext.HbDealIDConstantKey)+"_nodeal"]; exists { - t.Error(string(openrtb_ext.HbDealIDConstantKey) + " key for nodeal bidder was not parsed correctly") - } - } - } -} - -var ( - MaxValueLength = 1024 * 10 - MaxNumValues = 10 -) - -type responseObject struct { - UUID string `json:"uuid"` -} - -type response struct { - Responses []responseObject `json:"responses"` -} - -type putAnyObject struct { - Type string `json:"type"` - Value json.RawMessage `json:"value"` -} - -type putAnyRequest struct { - Puts []putAnyObject `json:"puts"` -} - -func DummyPrebidCacheServer(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read the request body.", http.StatusBadRequest) - return - } - defer r.Body.Close() - var put putAnyRequest - - err = json.Unmarshal(body, &put) - if err != nil { - http.Error(w, "Request body "+string(body)+" is not valid JSON.", http.StatusBadRequest) - return - } - - if len(put.Puts) > MaxNumValues { - http.Error(w, fmt.Sprintf("More keys than allowed: %d", MaxNumValues), http.StatusBadRequest) - return - } - - resp := response{ - Responses: make([]responseObject, len(put.Puts)), - } - for i, p := range put.Puts { - resp.Responses[i].UUID = fmt.Sprintf("UUID-%d", i+1) // deterministic for testing - if len(p.Value) > MaxValueLength { - http.Error(w, fmt.Sprintf("Value is larger than allowed size: %d", MaxValueLength), http.StatusBadRequest) - return - } - if len(p.Value) == 0 { - http.Error(w, "Missing value.", http.StatusBadRequest) - return - } - if p.Type != "xml" && p.Type != "json" { - http.Error(w, fmt.Sprintf("Type must be one of [\"json\", \"xml\"]. Found %v", p.Type), http.StatusBadRequest) - return - } - } - - b, err := json.Marshal(&resp) - if err != nil { - http.Error(w, "Failed to serialize UUIDs into JSON.", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Write(b) -} - -func TestCacheVideoOnly(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer)) - defer server.Close() - - bids := make(pbs.PBSBidSlice, 0) - fbBid := pbs.PBSBid{ - BidID: "test_bidid0", - AdUnitCode: "test_adunitcode0", - BidderCode: "audienceNetwork", - Price: 2.00, - Adm: "fb_test_adm", - Width: 300, - Height: 250, - DealId: "2345", - CreativeMediaType: "video", - } - bids = append(bids, &fbBid) - anBid := pbs.PBSBid{ - BidID: "test_bidid1", - AdUnitCode: "test_adunitcode1", - BidderCode: "appnexus", - Price: 1.00, - Adm: "an_test_adm", - Width: 320, - Height: 50, - DealId: "1234", - CreativeMediaType: "banner", - } - bids = append(bids, &anBid) - rbBannerBid := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode2", - BidderCode: "rubicon", - Price: 1.00, - Adm: "rb_banner_test_adm", - Width: 300, - Height: 250, - DealId: "7890", - CreativeMediaType: "banner", - } - bids = append(bids, &rbBannerBid) - rbVideoBid1 := pbs.PBSBid{ - BidID: "test_bidid3", - AdUnitCode: "test_adunitcode3", - BidderCode: "rubicon", - Price: 1.00, - Adm: "rb_video_test_adm1", - Width: 300, - Height: 250, - DealId: "7890", - CreativeMediaType: "video", - } - bids = append(bids, &rbVideoBid1) - rbVideoBid2 := pbs.PBSBid{ - BidID: "test_bidid4", - AdUnitCode: "test_adunitcode4", - BidderCode: "rubicon", - Price: 1.00, - Adm: "rb_video_test_adm2", - Width: 300, - Height: 250, - DealId: "7890", - CreativeMediaType: "video", - } - bids = append(bids, &rbVideoBid2) - - 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()) - } - syncersByBidder := map[string]usersync.Syncer{} - gdprPerms := gdpr.NewPermissions(context.Background(), config.GDPR{ - HostVendorID: 0, - }, nil, nil) - prebid_cache_client.InitPrebidCache(server.URL) - var labels = &metrics.Labels{} - if err := cacheVideoOnly(bids, ctx, &auction{cfg: cfg, syncersByBidder: syncersByBidder, gdprPerms: gdprPerms, metricsEngine: &metricsConf.NilMetricsEngine{}}, labels); err != nil { - t.Errorf("Prebid cache failed: %v \n", err) - return - } - if bids[0].CacheID != "UUID-1" { - t.Errorf("UUID was '%s', should have been 'UUID-1'", bids[0].CacheID) - } - if bids[1].CacheID != "" { - t.Errorf("UUID was '%s', should have been empty", bids[1].CacheID) - } - if bids[2].CacheID != "" { - t.Errorf("UUID was '%s', should have been empty", bids[2].CacheID) - } - if bids[3].CacheID != "UUID-2" { - t.Errorf("First object UUID was '%s', should have been 'UUID-2'", bids[3].CacheID) - } - if bids[4].CacheID != "UUID-3" { - t.Errorf("Second object UUID was '%s', should have been 'UUID-3'", bids[4].CacheID) - } -} - -func TestShouldUsersync(t *testing.T) { - tests := []struct { - description string - signal string - allowHostCookies bool - allowBidderSync bool - wantAllow bool - }{ - { - description: "Don't sync - GDPR on, host cookies disallows and bidder sync disallows", - signal: "1", - allowHostCookies: false, - allowBidderSync: false, - wantAllow: false, - }, - { - description: "Don't sync - GDPR on, host cookies disallows and bidder sync allows", - signal: "1", - allowHostCookies: false, - allowBidderSync: true, - wantAllow: false, - }, - { - description: "Don't sync - GDPR on, host cookies allows and bidder sync disallows", - signal: "1", - allowHostCookies: true, - allowBidderSync: false, - wantAllow: false, - }, - { - description: "Sync - GDPR on, host cookies allows and bidder sync allows", - signal: "1", - allowHostCookies: true, - allowBidderSync: true, - wantAllow: true, - }, - { - description: "Don't sync - invalid GDPR signal, host cookies disallows and bidder sync disallows", - signal: "2", - allowHostCookies: false, - allowBidderSync: false, - wantAllow: false, - }, - } - - for _, tt := range tests { - deps := auction{ - gdprPerms: &auctionMockPermissions{ - allowBidderSync: tt.allowBidderSync, - allowHostCookies: tt.allowHostCookies, - }, - } - gdprPrivacyPolicy := gdprPolicy.Policy{ - Signal: tt.signal, - } - - allow := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, gdprPrivacyPolicy) - assert.Equal(t, tt.wantAllow, allow, tt.description) - } -} - -type auctionMockPermissions struct { - allowBidderSync bool - allowHostCookies bool - allowBidRequest bool - passGeo bool - passID bool -} - -func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { - return m.allowHostCookies, nil -} - -func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) { - return m.allowBidderSync, 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) { - bids := make(pbs.PBSBidSlice, 0) - // bid1 will be rejected due to undefined size when adunit has multiple sizes - bid1 := pbs.PBSBid{ - BidID: "test_bidid1", - AdUnitCode: "test_adunitcode1", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - // Width: 100, - // Height: 100, - CreativeMediaType: "banner", - } - bids = append(bids, &bid1) - // bid2 will be considered a normal ideal banner bid - bid2 := pbs.PBSBid{ - BidID: "test_bidid2", - AdUnitCode: "test_adunitcode2", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - Width: 100, - Height: 100, - CreativeMediaType: "banner", - } - bids = append(bids, &bid2) - // bid3 will have it's dimensions set based on sizes defined in request - bid3 := pbs.PBSBid{ - BidID: "test_bidid3", - AdUnitCode: "test_adunitcode3", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - //Width: 200, - //Height: 200, - CreativeMediaType: "banner", - } - - bids = append(bids, &bid3) - - // bid4 will be ignored as it's a video creative type - bid4 := pbs.PBSBid{ - BidID: "test_bidid_video", - AdUnitCode: "test_adunitcode_video", - BidderCode: "randNetwork", - Price: 1.05, - Adm: "test_adm", - //Width: 400, - //Height: 400, - CreativeMediaType: "video", - } - - bids = append(bids, &bid4) - - mybidder := pbs.PBSBidder{ - BidderCode: "randNetwork", - AdUnitCode: "test_adunitcode", - AdUnits: []pbs.PBSAdUnit{ - { - BidID: "test_bidid1", - Sizes: []openrtb2.Format{ - { - W: 350, - H: 250, - }, - { - W: 300, - H: 50, - }, - }, - Code: "test_adunitcode1", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid2", - Sizes: []openrtb2.Format{ - { - W: 100, - H: 100, - }, - }, - Code: "test_adunitcode2", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid3", - Sizes: []openrtb2.Format{ - { - W: 200, - H: 200, - }, - }, - Code: "test_adunitcode3", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid_video", - Sizes: []openrtb2.Format{ - { - W: 400, - H: 400, - }, - }, - Code: "test_adunitcode_video", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_VIDEO, - }, - }, - { - BidID: "test_bidid3", - Sizes: []openrtb2.Format{ - { - W: 150, - H: 150, - }, - }, - Code: "test_adunitcode_x", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - { - BidID: "test_bidid_y", - Sizes: []openrtb2.Format{ - { - W: 150, - H: 150, - }, - }, - Code: "test_adunitcode_3", - MediaTypes: []pbs.MediaType{ - pbs.MEDIA_TYPE_BANNER, - }, - }, - }, - } - - bids = checkForValidBidSize(bids, &mybidder) - - testdata, _ := json.MarshalIndent(bids, "", " ") - if len(bids) != 3 { - t.Errorf("Detected returned bid list did not contain only 3 bid objects as expected.\nBelow is the contents of the bid list\n%v", string(testdata)) - } - - for _, bid := range bids { - if bid.BidID == "test_bidid3" { - if bid.Width == 0 && bid.Height == 0 { - t.Errorf("Detected the Width & Height attributes in test bidID %v were not set to the dimensions used from the mybidder object", bid.BidID) - } - } - } -} - -func TestWriteAuctionError(t *testing.T) { - recorder := httptest.NewRecorder() - writeAuctionError(recorder, "some error message", nil) - var resp pbs.PBSResponse - json.Unmarshal(recorder.Body.Bytes(), &resp) - - if len(resp.Bids) != 0 { - t.Error("Error responses should return no bids.") - } - if resp.Status != "some error message" { - t.Errorf("The response status should be the error message. Got: %s", resp.Status) - } - - if len(resp.BidderStatus) != 0 { - t.Errorf("Error responses shouldn't have any BidderStatus elements. Got %d", len(resp.BidderStatus)) - } -} - -func TestPanicRecovery(t *testing.T) { - testAuction := auction{ - cfg: nil, - syncersByBidder: nil, - gdprPerms: &auctionMockPermissions{ - allowBidderSync: false, - allowHostCookies: false, - }, - metricsEngine: &metricsConf.NilMetricsEngine{}, - } - panicker := func(bidder *pbs.PBSBidder, blables metrics.AdapterLabels) { - panic("panic!") - } - recovered := testAuction.recoverSafely(panicker) - recovered(nil, metrics.AdapterLabels{}) -} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 790a8d6482d..eb6f17f2359 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1572,15 +1572,6 @@ func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) } } -// parseUserID gets this user's ID for the host machine, if it exists. -func parseUserID(cfg *config.Configuration, httpReq *http.Request) (string, bool) { - if hostCookie, err := httpReq.Cookie(cfg.HostCookie.CookieName); hostCookie != nil && err == nil { - return hostCookie.Value, true - } else { - return "", false - } -} - // Write(return) errors to the client, if any. Returns true if errors were found. func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) bool { var rc bool = false diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index a259719ba8a..20fdd56e74b 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -25,7 +25,6 @@ import ( "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "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" @@ -3725,21 +3724,6 @@ func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []s return testStoredRequestData, testStoredImpData, nil } -var mockAccountData = map[string]json.RawMessage{ - "valid_acct": json.RawMessage(`{"disabled":false}`), -} - -type mockAccountFetcher struct { -} - -func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) { - if account, ok := mockAccountData[accountID]; ok { - return account, nil - } else { - return nil, []error{stored_requests.NotFoundError{accountID, "Account"}} - } -} - type mockExchange struct { lastRequest *openrtb2.BidRequest } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 3163cd9d323..b7eb0b27c0b 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1435,19 +1435,3 @@ var testVideoStoredImpData = map[string]json.RawMessage{ var testVideoStoredRequestData = map[string]json.RawMessage{ "80ce30c53c16e6ede735f123ef6e32361bfc7b22": json.RawMessage(`{"accountid": "11223344", "site": {"page": "mygame.foo.com"}}`), } - -func loadValidRequest(t *testing.T) *openrtb_ext.BidRequestVideo { - reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json") - if err != nil { - t.Fatalf("Failed to fetch a valid request: %v", err) - } - - reqBody := getRequestPayload(t, reqData) - - reqVideo := &openrtb_ext.BidRequestVideo{} - if err := json.Unmarshal(reqBody, reqVideo); err != nil { - t.Fatalf("Failed to unmarshal the request: %v", err) - } - - return reqVideo -} diff --git a/exchange/auction.go b/exchange/auction.go index 94e808801d9..c8aff684e41 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -311,13 +311,6 @@ func valOrZero(useVal bool, val int) int { return 0 } -func maybeMake(shouldMake bool, capacity int) []prebid_cache_client.Cacheable { - if shouldMake { - return make([]prebid_cache_client.Cacheable, 0, capacity) - } - return nil -} - func cacheTTL(impTTL int64, bidTTL int64, defTTL int64, buffer int64) (ttl int64) { if impTTL <= 0 && bidTTL <= 0 { // Only use default if there is no imp nor bid TTL provided. We don't want the default diff --git a/exchange/auction_test.go b/exchange/auction_test.go index ee064fcb6f1..455ae5018e8 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -555,12 +555,6 @@ type pbsBid struct { Bidder openrtb_ext.BidderName `json:"bidder"` } -type cacheComparator struct { - freq int - expectedKeys []string - actualKeys []string -} - type mockCache struct { scheme string host string diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index da31658e32d..82f058514f7 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -1795,7 +1795,6 @@ func (bidder *mixedMultiBidder) MakeBids(internalRequest *openrtb2.BidRequest, e } type bidRejector struct { - httpRequest *adapters.RequestData httpResponse *adapters.ResponseData } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 9dcf9d66a7f..b4fd270d023 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -2997,7 +2997,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) + bidCategory, _, rejections, err := applyCategoryMapping(nil, &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") diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index 8991a116624..48e054c7bda 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,17 +8,14 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/gdpr" - - metricsConf "github.com/prebid/prebid-server/metrics/config" metricsConfig "github.com/prebid/prebid-server/metrics/config" - - "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/openrtb_ext" + + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -88,7 +85,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op ex := &exchange{ adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), - me: &metricsConf.NilMetricsEngine{}, + me: &metricsConfig.NilMetricsEngine{}, cache: &wellBehavedCache{}, cacheTime: time.Duration(0), gDPR: gdpr.AlwaysAllow{}, @@ -129,14 +126,6 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op return buildBidMap(bidResp.SeatBid, len(mockBids)) } -func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb2.Bid) []openrtb_ext.BidderName { - bidders := make([]openrtb_ext.BidderName, 0, len(bids)) - for name := range bids { - bidders = append(bidders, name) - } - return bidders -} - func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]adaptedBidder { adapterMap := make(map[openrtb_ext.BidderName]adaptedBidder, len(bids)) for bidder, bids := range bids { diff --git a/go.mod b/go.mod index 347f36bcf86..e673d2218c7 100644 --- a/go.mod +++ b/go.mod @@ -8,20 +8,17 @@ require ( github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect - github.com/blang/semver v3.5.1+incompatible 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 github.com/docker/go-units v0.4.0 - github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/influxdata/influxdb v1.6.1 github.com/julienschmidt/httprouter v1.1.0 github.com/lib/pq v1.0.0 - github.com/magiconair/properties v1.8.5 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 diff --git a/go.sum b/go.sum index 31e6b8fc93f..bcab99e4ebf 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLM github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -87,8 +85,6 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= diff --git a/main.go b/main.go index 6087c3d69dd..76fa64f77ef 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" - pbc "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/router" "github.com/prebid/prebid-server/server" "github.com/prebid/prebid-server/util/task" @@ -56,8 +55,6 @@ func serve(cfg *config.Configuration) error { return err } - pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL()) - corsRouter := router.SupportCORS(r) server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(currencyConverter, fetchingInterval), r.MetricsEngine) diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 51ba8cafe2f..66849db5864 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -90,13 +90,6 @@ func (me *MultiMetricsEngine) RecordImps(implabels metrics.ImpLabels) { } } -// RecordImps for the legacy endpoint -func (me *MultiMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { - for _, thisME := range *me { - thisME.RecordLegacyImps(labels, numImps) - } -} - // RecordRequestTime across all engines func (me *MultiMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { for _, thisME := range *me { @@ -278,10 +271,6 @@ func (me *NilMetricsEngine) RecordConnectionClose(success bool) { func (me *NilMetricsEngine) RecordImps(implabels metrics.ImpLabels) { } -// RecordLegacyImps as a noop -func (me *NilMetricsEngine) RecordLegacyImps(labels metrics.Labels, numImps int) { -} - // RecordRequestTime as a noop func (me *NilMetricsEngine) RecordRequestTime(labels metrics.Labels, length time.Duration) { } diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 0d6bcdb922d..e4227afda77 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -78,7 +78,6 @@ func TestMultiMetricsEngine(t *testing.T) { for i := 0; i < 5; i++ { metricsEngine.RecordRequest(labels) metricsEngine.RecordImps(impTypeLabels) - metricsEngine.RecordLegacyImps(labels, 2) metricsEngine.RecordRequestTime(labels, time.Millisecond*20) metricsEngine.RecordAdapterRequest(pubLabels) metricsEngine.RecordAdapterRequest(apnLabels) @@ -147,7 +146,6 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "Request", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusOK].Count(), 5) VerifyMetrics(t, "ImpMeter", goEngine.ImpMeter.Count(), 8) - VerifyMetrics(t, "LegacyImpMeter", goEngine.LegacyImpMeter.Count(), 10) VerifyMetrics(t, "NoCookieMeter", goEngine.NoCookieMeter.Count(), 0) VerifyMetrics(t, "AdapterMetrics.Pubmatic.GotBidsMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].GotBidsMeter.Count(), 5) VerifyMetrics(t, "AdapterMetrics.Pubmatic.NoBidMeter", goEngine.AdapterMetrics[openrtb_ext.BidderPubmatic].NoBidMeter.Count(), 0) diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 615b83b8be9..c93f10602c7 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -18,7 +18,6 @@ type Metrics struct { ConnectionAcceptErrorMeter metrics.Meter ConnectionCloseErrorMeter metrics.Meter ImpMeter metrics.Meter - LegacyImpMeter metrics.Meter AppRequestMeter metrics.Meter NoCookieMeter metrics.Meter RequestTimer metrics.Timer @@ -101,9 +100,6 @@ type accountMetrics struct { adapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics } -// Defining an "unknown" bidder -const unknownBidder openrtb_ext.BidderName = "unknown" - // NewBlankMetrics creates a new Metrics object with all blank metrics object. This may also be useful for // testing routines to ensure that no metrics are written anywhere. // @@ -122,7 +118,6 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa ConnectionAcceptErrorMeter: blankMeter, ConnectionCloseErrorMeter: blankMeter, ImpMeter: blankMeter, - LegacyImpMeter: blankMeter, AppRequestMeter: blankMeter, NoCookieMeter: blankMeter, RequestTimer: blankTimer, @@ -216,7 +211,6 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.ConnectionAcceptErrorMeter = metrics.GetOrRegisterMeter("connection_accept_errors", registry) newMetrics.ConnectionCloseErrorMeter = metrics.GetOrRegisterMeter("connection_close_errors", registry) newMetrics.ImpMeter = metrics.GetOrRegisterMeter("imps_requested", registry) - newMetrics.LegacyImpMeter = metrics.GetOrRegisterMeter("legacy_imps_requested", registry) newMetrics.ImpsTypeBanner = metrics.GetOrRegisterMeter("imp_banner", registry) newMetrics.ImpsTypeVideo = metrics.GetOrRegisterMeter("imp_video", registry) @@ -452,10 +446,6 @@ func (me *Metrics) RecordImps(labels ImpLabels) { } } -func (me *Metrics) RecordLegacyImps(labels Labels, numImps int) { - me.LegacyImpMeter.Mark(int64(numImps)) -} - func (me *Metrics) RecordConnectionAccept(success bool) { if success { me.ConnectionCounter.Inc(1) diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 346a64a737f..dd2430d6b74 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -36,10 +36,6 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "prebid_cache_request_time.ok", m.PrebidCacheRequestTimerSuccess) ensureContains(t, registry, "prebid_cache_request_time.err", m.PrebidCacheRequestTimerError) - ensureContains(t, registry, "requests.ok.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusOK]) - ensureContains(t, registry, "requests.badinput.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusBadInput]) - ensureContains(t, registry, "requests.err.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusErr]) - ensureContains(t, registry, "requests.networkerr.legacy", m.RequestStatuses[ReqTypeLegacy][RequestStatusNetworkErr]) ensureContains(t, registry, "requests.ok.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusOK]) ensureContains(t, registry, "requests.badinput.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusBadInput]) ensureContains(t, registry, "requests.err.openrtb2-web", m.RequestStatuses[ReqTypeORTB2Web][RequestStatusErr]) diff --git a/metrics/metrics.go b/metrics/metrics.go index af45f9b4f5a..9d16143f0d4 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -382,7 +382,6 @@ type MetricsEngine interface { RecordConnectionClose(success bool) RecordRequest(labels Labels) // ignores adapter. only statusOk and statusErr fom status RecordImps(labels ImpLabels) // RecordImps across openRTB2 engines that support the 'Native' Imp Type - RecordLegacyImps(labels Labels, numImps int) // RecordImps for the legacy engine RecordRequestTime(labels Labels, length time.Duration) // ignores adapter. only statusOk and statusErr fom status RecordAdapterRequest(labels AdapterLabels) RecordAdapterConnections(adapterName openrtb_ext.BidderName, connWasReused bool, connWaitTime time.Duration) diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index b8ab23b768a..c8d5311c3a4 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -32,11 +32,6 @@ func (me *MetricsEngineMock) RecordImps(labels ImpLabels) { me.Called(labels) } -// RecordLegacyImps mock -func (me *MetricsEngineMock) RecordLegacyImps(labels Labels, numImps int) { - me.Called(labels, numImps) -} - // RecordRequestTime mock func (me *MetricsEngineMock) RecordRequestTime(labels Labels, length time.Duration) { me.Called(labels, length) diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 52470369094..53a5afabd53 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -476,10 +476,6 @@ func (m *Metrics) RecordImps(labels metrics.ImpLabels) { }).Inc() } -func (m *Metrics) RecordLegacyImps(labels metrics.Labels, numImps int) { - m.impressionsLegacy.Add(float64(numImps)) -} - func (m *Metrics) RecordRequestTime(labels metrics.Labels, length time.Duration) { if labels.RequestStatus == metrics.RequestStatusOK { m.requestsTimer.With(prometheus.Labels{ diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 0fe852b81df..fc8abdb9d04 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -355,16 +355,6 @@ func TestImpressionsMetric(t *testing.T) { } } -func TestLegacyImpressionsMetric(t *testing.T) { - m := createMetricsForTesting() - - m.RecordLegacyImps(metrics.Labels{}, 42) - - expectedCount := float64(42) - assertCounterValue(t, "", "impressionsLegacy", m.impressionsLegacy, - expectedCount) -} - func TestRequestTimeMetric(t *testing.T) { requestType := metrics.ReqTypeORTB2Web performTest := func(m *Metrics, requestStatus metrics.RequestStatus, timeInMs float64) { @@ -1162,18 +1152,6 @@ func TestPrebidCacheRequestTimeMetric(t *testing.T) { assertHistogram(t, "Error", errorResult, errorExpectedCount, errorExpectedSum) } -func TestMetricAccumulationSpotCheck(t *testing.T) { - m := createMetricsForTesting() - - m.RecordLegacyImps(metrics.Labels{}, 1) - m.RecordLegacyImps(metrics.Labels{}, 2) - m.RecordLegacyImps(metrics.Labels{}, 3) - - expectedValue := float64(1 + 2 + 3) - assertCounterValue(t, "", "impressionsLegacy", m.impressionsLegacy, - expectedValue) -} - func TestRecordRequestQueueTimeMetric(t *testing.T) { performTest := func(m *Metrics, requestStatus bool, requestType metrics.RequestType, timeInSec float64) { m.RecordRequestQueueTime(requestStatus, requestType, time.Duration(timeInSec*float64(time.Second))) diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index 4e2d1ff5ba1..d99f2f6f39b 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -104,15 +104,6 @@ func setUidStatusesAsString() []string { return valuesAsString } -func storedDataTypesAsString() []string { - values := metrics.StoredDataTypes() - valuesAsString := make([]string, len(values)) - for i, v := range values { - valuesAsString[i] = string(v) - } - return valuesAsString -} - func storedDataFetchTypesAsString() []string { values := metrics.StoredDataFetchTypes() valuesAsString := make([]string, len(values)) diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 7976451877c..14a261e314e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -12,8 +12,6 @@ import ( "github.com/xeipuuv/gojsonschema" ) -const schemaDirectory = "static/bidder-params" - // BidderName refers to a core bidder id or an alias id. type BidderName string diff --git a/openrtb_ext/imp_beachfront.go b/openrtb_ext/imp_beachfront.go index 104ca8f9fb7..9e65d54b82b 100644 --- a/openrtb_ext/imp_beachfront.go +++ b/openrtb_ext/imp_beachfront.go @@ -4,10 +4,10 @@ type ExtImpBeachfront struct { AppId string `json:"appId"` AppIds ExtImpBeachfrontAppIds `json:"appIds"` BidFloor float64 `json:"bidfloor"` - VideoResponseType string `json:"videoResponseType, omitempty"` + VideoResponseType string `json:"videoResponseType,omitempty"` } type ExtImpBeachfrontAppIds struct { - Video string `json:"video, omitempty"` - Banner string `json:"banner, omitempty"` + Video string `json:"video,omitempty"` + Banner string `json:"banner,omitempty"` } diff --git a/openrtb_ext/imp_nanointeractive.go b/openrtb_ext/imp_nanointeractive.go index 28db5be0d07..77e386237ac 100644 --- a/openrtb_ext/imp_nanointeractive.go +++ b/openrtb_ext/imp_nanointeractive.go @@ -3,8 +3,8 @@ package openrtb_ext // ExtImpNanoInteractive defines the contract for bidrequest.imp[i].ext.nanointeractive type ExtImpNanoInteractive struct { Pid string `json:"pid"` - Nq []string `json:"nq, omitempty"` - Category string `json:"category, omitempty"` - SubId string `json:"subId, omitempty"` - Ref string `json:"ref, omitempty"` + Nq []string `json:"nq,omitempty"` + Category string `json:"category,omitempty"` + SubId string `json:"subId,omitempty"` + Ref string `json:"ref,omitempty"` } diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go deleted file mode 100644 index c05b9a8c00d..00000000000 --- a/pbs/pbsrequest.go +++ /dev/null @@ -1,403 +0,0 @@ -package pbs - -import ( - "encoding/json" - "fmt" - "math/rand" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/prebid/prebid-server/cache" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prebid/prebid-server/util/httputil" - "github.com/prebid/prebid-server/util/iputil" - - "github.com/blang/semver" - "github.com/buger/jsonparser" - "github.com/golang/glog" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "golang.org/x/net/publicsuffix" -) - -const MAX_BIDDERS = 8 - -type MediaType byte - -const ( - MEDIA_TYPE_BANNER MediaType = iota - MEDIA_TYPE_VIDEO -) - -type ConfigCache interface { - LoadConfig(string) ([]Bids, error) -} - -type Bids struct { - BidderCode string `json:"bidder"` - BidID string `json:"bid_id"` - Params json.RawMessage `json:"params"` -} - -// Structure for holding video-specific information -type PBSVideo struct { - //Content MIME types supported. Popular MIME types may include “video/x-ms-wmv” for Windows Media and “video/x-flv” for Flash Video. - Mimes []string `json:"mimes,omitempty"` - - //Minimum video ad duration in seconds. - Minduration int64 `json:"minduration,omitempty"` - - // Maximum video ad duration in seconds. - Maxduration int64 `json:"maxduration,omitempty"` - - //Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. - Startdelay int64 `json:"startdelay,omitempty"` - - // Indicates if the player will allow the video to be skipped ( 0 = no, 1 = yes). - Skippable int `json:"skippable,omitempty"` - - // Playback method code Description - // 1 - Initiates on Page Load with Sound On - // 2 - Initiates on Page Load with Sound Off by Default - // 3 - Initiates on Click with Sound On - // 4 - Initiates on Mouse-Over with Sound On - // 5 - Initiates on Entering Viewport with Sound On - // 6 - Initiates on Entering Viewport with Sound Off by Default - PlaybackMethod int8 `json:"playback_method,omitempty"` - - //protocols as specified in ORTB 5.8 - // 1 VAST 1.0 - // 2 VAST 2.0 - // 3 VAST 3.0 - // 4 VAST 1.0 Wrapper - // 5 VAST 2.0 Wrapper - // 6 VAST 3.0 Wrapper - // 7 VAST 4.0 - // 8 VAST 4.0 Wrapper - // 9 DAAST 1.0 - // 10 DAAST 1.0 Wrapper - Protocols []int8 `json:"protocols,omitempty"` -} - -type AdUnit struct { - Code string `json:"code"` - TopFrame int8 `json:"is_top_frame"` - Sizes []openrtb2.Format `json:"sizes"` - Bids []Bids `json:"bids"` - ConfigID string `json:"config_id"` - MediaTypes []string `json:"media_types"` - Instl int8 `json:"instl"` - Video PBSVideo `json:"video"` -} - -type PBSAdUnit struct { - Sizes []openrtb2.Format - TopFrame int8 - Code string - BidID string - Params json.RawMessage - Video PBSVideo - MediaTypes []MediaType - Instl int8 -} - -func ParseMediaType(s string) (MediaType, error) { - mediaTypes := map[string]MediaType{"BANNER": MEDIA_TYPE_BANNER, "VIDEO": MEDIA_TYPE_VIDEO} - t, ok := mediaTypes[strings.ToUpper(s)] - if !ok { - return 0, fmt.Errorf("Invalid MediaType %s", s) - } - return t, nil -} - -type SDK struct { - Version string `json:"version"` - Source string `json:"source"` - Platform string `json:"platform"` -} - -type PBSBidder struct { - BidderCode string `json:"bidder"` - AdUnitCode string `json:"ad_unit,omitempty"` // for index to dedup responses - ResponseTime int `json:"response_time_ms,omitempty"` - NumBids int `json:"num_bids,omitempty"` - Error string `json:"error,omitempty"` - NoCookie bool `json:"no_cookie,omitempty"` - NoBid bool `json:"no_bid,omitempty"` - UsersyncInfo *UsersyncInfo `json:"usersync,omitempty"` - Debug []*BidderDebug `json:"debug,omitempty"` - - AdUnits []PBSAdUnit `json:"-"` -} - -type UsersyncInfo struct { - URL string `json:"url,omitempty"` - Type string `json:"type,omitempty"` - SupportCORS bool `json:"supportCORS,omitempty"` -} - -func (bidder *PBSBidder) LookupBidID(Code string) string { - for _, unit := range bidder.AdUnits { - if unit.Code == Code { - return unit.BidID - } - } - return "" -} - -func (bidder *PBSBidder) LookupAdUnit(Code string) (unit *PBSAdUnit) { - for _, unit := range bidder.AdUnits { - if unit.Code == Code { - return &unit - } - } - return nil -} - -type PBSRequest struct { - AccountID string `json:"account_id"` - Tid string `json:"tid"` - CacheMarkup int8 `json:"cache_markup"` - SortBids int8 `json:"sort_bids"` - MaxKeyLength int8 `json:"max_key_length"` - Secure int8 `json:"secure"` - TimeoutMillis int64 `json:"timeout_millis"` - AdUnits []AdUnit `json:"ad_units"` - IsDebug bool `json:"is_debug"` - App *openrtb2.App `json:"app"` - Device *openrtb2.Device `json:"device"` - PBSUser json.RawMessage `json:"user"` - SDK *SDK `json:"sdk"` - - // internal - Bidders []*PBSBidder `json:"-"` - User *openrtb2.User `json:"-"` - Cookie *usersync.Cookie `json:"-"` - Url string `json:"-"` - Domain string `json:"-"` - Regs *openrtb2.Regs `json:"-"` - Start time.Time -} - -func ConfigGet(cache cache.Cache, id string) ([]Bids, error) { - conf, err := cache.Config().Get(id) - if err != nil { - return nil, err - } - - bids := make([]Bids, 0) - err = json.Unmarshal([]byte(conf), &bids) - if err != nil { - return nil, err - } - - return bids, nil -} - -func ParseMediaTypes(types []string) []MediaType { - var mtypes []MediaType - mtmap := make(map[MediaType]bool) - - if types == nil { - mtypes = append(mtypes, MEDIA_TYPE_BANNER) - } else { - for _, t := range types { - mt, er := ParseMediaType(t) - if er != nil { - glog.Infof("Invalid media type: %s", er) - } else { - if !mtmap[mt] { - mtypes = append(mtypes, mt) - mtmap[mt] = true - } - } - } - if len(mtypes) == 0 { - mtypes = append(mtypes, MEDIA_TYPE_BANNER) - } - } - return mtypes -} - -var ipv4Validator iputil.IPValidator = iputil.VersionIPValidator{Version: iputil.IPv4} - -func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.Cache, hostCookieConfig *config.HostCookie) (*PBSRequest, error) { - defer r.Body.Close() - - pbsReq := &PBSRequest{} - err := json.NewDecoder(r.Body).Decode(&pbsReq) - if err != nil { - return nil, err - } - pbsReq.Start = time.Now() - - if len(pbsReq.AdUnits) == 0 { - return nil, fmt.Errorf("No ad units specified") - } - - pbsReq.TimeoutMillis = int64(cfg.LimitAuctionTimeout(time.Duration(pbsReq.TimeoutMillis)*time.Millisecond) / time.Millisecond) - - if pbsReq.Device == nil { - pbsReq.Device = &openrtb2.Device{} - } - if ip, _ := httputil.FindIP(r, ipv4Validator); ip != nil { - pbsReq.Device.IP = ip.String() - } - - if pbsReq.SDK == nil { - pbsReq.SDK = &SDK{} - } - - // Early versions of prebid mobile are sending requests with gender indicated by numbers, - // those traffic can't be parsed by latest Prebid Server after the change of gender to use string so clients using early versions can't be monetized. - // To handle those traffic, adding a check here to ignore the sent gender for versions lower than 0.0.2. - v1, err := semver.Make(pbsReq.SDK.Version) - v2, err := semver.Make("0.0.2") - if v1.Compare(v2) >= 0 && pbsReq.PBSUser != nil { - err = json.Unmarshal([]byte(pbsReq.PBSUser), &pbsReq.User) - if err != nil { - return nil, err - } - } - - if pbsReq.User == nil { - pbsReq.User = &openrtb2.User{} - } - - // use client-side data for web requests - if pbsReq.App == nil { - pbsReq.Cookie = usersync.ParseCookieFromRequest(r, hostCookieConfig) - - pbsReq.Device.UA = r.Header.Get("User-Agent") - - pbsReq.Url = r.Header.Get("Referer") // must be specified in the header - // TODO: this should explicitly put us in test mode - if r.FormValue("url_override") != "" { - pbsReq.Url = r.FormValue("url_override") - } - if strings.Index(pbsReq.Url, "http") == -1 { - pbsReq.Url = fmt.Sprintf("http://%s", pbsReq.Url) - } - - url, err := url.Parse(pbsReq.Url) - if err != nil { - return nil, fmt.Errorf("Invalid URL '%s': %v", pbsReq.Url, err) - } - - if url.Host == "" { - return nil, fmt.Errorf("Host not found from URL '%v'", url) - } - - pbsReq.Domain, err = publicsuffix.EffectiveTLDPlusOne(url.Host) - if err != nil { - return nil, fmt.Errorf("Invalid URL '%s': %v", url.Host, err) - } - } - - if r.FormValue("debug") == "1" { - pbsReq.IsDebug = true - } - - if httputil.IsSecure(r) { - pbsReq.Secure = 1 - } - - pbsReq.Bidders = make([]*PBSBidder, 0, MAX_BIDDERS) - - for _, unit := range pbsReq.AdUnits { - bidders := unit.Bids - if unit.ConfigID != "" { - bidders, err = ConfigGet(cache, unit.ConfigID) - if err != nil { - if _, notFound := err.(*stored_requests.NotFoundError); !notFound { - glog.Warningf("Failed to load config '%s' from cache: %v", unit.ConfigID, err) - } - // proceed with other ad units - continue - } - } - - if glog.V(2) { - glog.Infof("Ad unit %s has %d bidders for %d sizes", unit.Code, len(bidders), len(unit.Sizes)) - } - - mtypes := ParseMediaTypes(unit.MediaTypes) - for _, b := range bidders { - var bidder *PBSBidder - for _, pb := range pbsReq.Bidders { - if pb.BidderCode == b.BidderCode { - bidder = pb - } - } - - if bidder == nil { - bidder = &PBSBidder{BidderCode: b.BidderCode} - pbsReq.Bidders = append(pbsReq.Bidders, bidder) - } - if b.BidID == "" { - b.BidID = fmt.Sprintf("%d", rand.Int63()) - } - - pau := PBSAdUnit{ - Sizes: unit.Sizes, - TopFrame: unit.TopFrame, - Code: unit.Code, - Instl: unit.Instl, - Params: b.Params, - BidID: b.BidID, - MediaTypes: mtypes, - Video: unit.Video, - } - - bidder.AdUnits = append(bidder.AdUnits, pau) - } - } - - return pbsReq, nil -} - -func (req PBSRequest) Elapsed() int { - return int(time.Since(req.Start) / 1000000) -} - -func (p PBSRequest) String() string { - b, _ := json.MarshalIndent(p, "", " ") - return string(b) -} - -// parses the "Regs.ext.gdpr" from the request, if it exists. Otherwise returns an empty string. -func (req *PBSRequest) ParseGDPR() string { - if req == nil || req.Regs == nil || len(req.Regs.Ext) == 0 { - return "" - } - val, err := jsonparser.GetInt(req.Regs.Ext, "gdpr") - if err != nil { - return "" - } - gdpr := strconv.Itoa(int(val)) - - return gdpr -} - -// parses the "User.ext.consent" from the request, if it exists. Otherwise returns an empty string. -func (req *PBSRequest) ParseConsent() string { - if req == nil || req.User == nil { - return "" - } - return parseString(req.User.Ext, "consent") -} - -func parseString(data []byte, key string) string { - if len(data) == 0 { - return "" - } - val, err := jsonparser.GetString(data, key) - if err != nil { - return "" - } - return val -} diff --git a/pbs/pbsrequest_test.go b/pbs/pbsrequest_test.go deleted file mode 100644 index 52cd6153323..00000000000 --- a/pbs/pbsrequest_test.go +++ /dev/null @@ -1,735 +0,0 @@ -package pbs - -import ( - "bytes" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/magiconair/properties/assert" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/config" -) - -const mimeVideoMp4 = "video/mp4" -const mimeVideoFlv = "video/x-flv" - -func TestParseMediaTypes(t *testing.T) { - types1 := []string{"Banner"} - t1 := ParseMediaTypes(types1) - assert.Equal(t, len(t1), 1) - assert.Equal(t, t1[0], MEDIA_TYPE_BANNER) - - types2 := []string{"Banner", "Video"} - t2 := ParseMediaTypes(types2) - assert.Equal(t, len(t2), 2) - assert.Equal(t, t2[0], MEDIA_TYPE_BANNER) - assert.Equal(t, t2[1], MEDIA_TYPE_VIDEO) - - types3 := []string{"Banner", "Vo"} - t3 := ParseMediaTypes(types3) - assert.Equal(t, len(t3), 1) - assert.Equal(t, t3[0], MEDIA_TYPE_BANNER) -} - -func TestParseSimpleRequest(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ] - }, - { - "code": "second", - "sizes": [{"w": 728, "h": 90}], - "media_types" :["banner", "video"], - "video" : { - "mimes" : ["video/mp4", "video/x-flv"] - }, - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ] - } - - ] - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 2 { - t.Errorf("Parse ad units failed") - } - - // see if our internal representation is intact - if len(pbs_req.Bidders) != 2 { - t.Fatalf("Should have two bidders not %d", len(pbs_req.Bidders)) - } - if pbs_req.Bidders[0].BidderCode != "ix" { - t.Errorf("First bidder not index") - } - if len(pbs_req.Bidders[0].AdUnits) != 2 { - t.Errorf("Index bidder should have 2 ad unit") - } - if pbs_req.Bidders[1].BidderCode != "appnexus" { - t.Errorf("Second bidder not appnexus") - } - if len(pbs_req.Bidders[1].AdUnits) != 2 { - t.Errorf("AppNexus bidder should have 2 ad unit") - } - if pbs_req.Bidders[1].AdUnits[0].BidID == "" { - t.Errorf("ID should have been generated for empty BidID") - } - if pbs_req.AdUnits[1].MediaTypes[0] != "banner" { - t.Errorf("Instead of banner MediaType received %s", pbs_req.AdUnits[1].MediaTypes[0]) - } - if pbs_req.AdUnits[1].MediaTypes[1] != "video" { - t.Errorf("Instead of video MediaType received %s", pbs_req.AdUnits[1].MediaTypes[0]) - } - if pbs_req.AdUnits[1].Video.Mimes[0] != mimeVideoMp4 { - t.Errorf("Instead of video/mp4 mimes received %s", pbs_req.AdUnits[1].Video.Mimes) - } - if pbs_req.AdUnits[1].Video.Mimes[1] != mimeVideoFlv { - t.Errorf("Instead of video/flv mimes received %s", pbs_req.AdUnits[1].Video.Mimes) - } - -} - -func TestHeaderParsing(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bidders": [ - { - "bidder": "ix", - "params": { - "id": "417", - "siteID": "test-site" - } - } - ] - } - ] - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - r.Header.Add("User-Agent", "Mozilla/") - d, _ := dummycache.New() - hcc := config.HostCookie{} - - d.Config().Set("dummy", dummyConfig) - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed") - } - if pbs_req.Url != "http://nytimes.com/cool.html" { - t.Errorf("Failed to pull URL from referrer") - } - if pbs_req.Domain != "nytimes.com" { - t.Errorf("Failed to parse TLD from referrer: %s not nytimes.com", pbs_req.Domain) - } - if pbs_req.Device.UA != "Mozilla/" { - t.Errorf("Failed to pull User-Agent from referrer") - } -} - -var dummyConfig = ` -[ - { - "bidder": "ix", - "bid_id": "22222222", - "params": { - "id": "4", - "siteID": "186774", - "timeout": "10000" - } - - }, - { - "bidder": "audienceNetwork", - "bid_id": "22222225", - "params": { - } - }, - { - "bidder": "pubmatic", - "bid_id": "22222223", - "params": { - "publisherId": "156009", - "adSlot": "39620189@728x90" - } - }, - { - "bidder": "appnexus", - "bid_id": "22222224", - "params": { - "placementId": "1" - } - } - ] - ` - -func TestParseConfig(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ] - }, - { - "code": "second", - "sizes": [{"w": 728, "h": 90}], - "config_id": "abcd" - } - ] - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - d, _ := dummycache.New() - hcc := config.HostCookie{} - - d.Config().Set("dummy", dummyConfig) - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 2 { - t.Errorf("Parse ad units failed") - } - - // see if our internal representation is intact - if len(pbs_req.Bidders) != 4 { - t.Fatalf("Should have 4 bidders not %d", len(pbs_req.Bidders)) - } - if pbs_req.Bidders[0].BidderCode != "ix" { - t.Errorf("First bidder not index") - } - if len(pbs_req.Bidders[0].AdUnits) != 2 { - t.Errorf("Index bidder should have 1 ad unit") - } - if pbs_req.Bidders[1].BidderCode != "appnexus" { - t.Errorf("Second bidder not appnexus") - } - if len(pbs_req.Bidders[1].AdUnits) != 2 { - t.Errorf("AppNexus bidder should have 2 ad unit") - } -} - -func TestParseMobileRequestFirstVersion(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":0, - "buyeruid":"test_buyeruid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"5d748364ee9c46a2b112892fc3551b6f" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.1" - }, - "sdk":{ - "version":"0.0.1", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 1 { - t.Errorf("Parse ad units failed") - } - // We are expecting all user fields to be nil. We don't parse user on v0.0.1 of prebid mobile - if pbs_req.User.BuyerUID != "" { - t.Errorf("Parse user buyeruid failed %s", pbs_req.User.BuyerUID) - } - if pbs_req.User.Gender != "" { - t.Errorf("Parse user gender failed %s", pbs_req.User.Gender) - } - if pbs_req.User.Yob != 0 { - t.Errorf("Parse user year of birth failed %d", pbs_req.User.Yob) - } - if pbs_req.User.ID != "" { - t.Errorf("Parse user id failed %s", pbs_req.User.ID) - } - - if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { - t.Errorf("Parse app bundle failed") - } - if pbs_req.App.Ver != "0.0.1" { - t.Errorf("Parse app version failed") - } - - if pbs_req.Device.IFA != "test_device_ifa" { - t.Errorf("Parse device ifa failed") - } - if pbs_req.Device.OSV != "9.3.5" { - t.Errorf("Parse device osv failed") - } - if pbs_req.Device.OS != "iOS" { - t.Errorf("Parse device os failed") - } - if pbs_req.Device.Make != "Apple" { - t.Errorf("Parse device make failed") - } - if pbs_req.Device.Model != "iPhone6,1" { - t.Errorf("Parse device model failed") - } -} - -func TestParseMobileRequest(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":"F", - "buyeruid":"test_buyeruid", - "yob":2000, - "id":"testid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"5d748364ee9c46a2b112892fc3551b6f" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.2" - }, - "sdk":{ - "version":"0.0.2", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 1 { - t.Errorf("Parse ad units failed") - } - - if pbs_req.User.BuyerUID != "test_buyeruid" { - t.Errorf("Parse user buyeruid failed") - } - if pbs_req.User.Gender != "F" { - t.Errorf("Parse user gender failed") - } - if pbs_req.User.Yob != 2000 { - t.Errorf("Parse user year of birth failed") - } - if pbs_req.User.ID != "testid" { - t.Errorf("Parse user id failed") - } - if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { - t.Errorf("Parse app bundle failed") - } - if pbs_req.App.Ver != "0.0.2" { - t.Errorf("Parse app version failed") - } - - if pbs_req.Device.IFA != "test_device_ifa" { - t.Errorf("Parse device ifa failed") - } - if pbs_req.Device.OSV != "9.3.5" { - t.Errorf("Parse device osv failed") - } - if pbs_req.Device.OS != "iOS" { - t.Errorf("Parse device os failed") - } - if pbs_req.Device.Make != "Apple" { - t.Errorf("Parse device make failed") - } - if pbs_req.Device.Model != "iPhone6,1" { - t.Errorf("Parse device model failed") - } - if pbs_req.SDK.Version != "0.0.2" { - t.Errorf("Parse sdk version failed") - } - if pbs_req.SDK.Source != "prebid-mobile" { - t.Errorf("Parse sdk source failed") - } - if pbs_req.SDK.Platform != "iOS" { - t.Errorf("Parse sdk platform failed") - } - if pbs_req.Device.IP == "" { - t.Errorf("Parse device ip failed %s", pbs_req.Device.IP) - } -} - -func TestParseMalformedMobileRequest(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":0, - "buyeruid":"test_buyeruid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "config_id":"ad5ffb41-3492-40f3-9c25-ade093eb4e5f", - "code":"5d748364ee9c46a2b112892fc3551b6f" - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.1" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if pbs_req.Tid != "abcd" { - t.Errorf("Parse TID failed") - } - if len(pbs_req.AdUnits) != 1 { - t.Errorf("Parse ad units failed") - } - // We are expecting all user fields to be nil. Since no SDK version is passed in - if pbs_req.User.BuyerUID != "" { - t.Errorf("Parse user buyeruid failed %s", pbs_req.User.BuyerUID) - } - if pbs_req.User.Gender != "" { - t.Errorf("Parse user gender failed %s", pbs_req.User.Gender) - } - if pbs_req.User.Yob != 0 { - t.Errorf("Parse user year of birth failed %d", pbs_req.User.Yob) - } - if pbs_req.User.ID != "" { - t.Errorf("Parse user id failed %s", pbs_req.User.ID) - } - - if pbs_req.App.Bundle != "AppNexus.PrebidMobileDemo" { - t.Errorf("Parse app bundle failed") - } - if pbs_req.App.Ver != "0.0.1" { - t.Errorf("Parse app version failed") - } - - if pbs_req.Device.IFA != "test_device_ifa" { - t.Errorf("Parse device ifa failed") - } - if pbs_req.Device.OSV != "9.3.5" { - t.Errorf("Parse device osv failed") - } - if pbs_req.Device.OS != "iOS" { - t.Errorf("Parse device os failed") - } - if pbs_req.Device.Make != "Apple" { - t.Errorf("Parse device make failed") - } - if pbs_req.Device.Model != "iPhone6,1" { - t.Errorf("Parse device model failed") - } -} - -func TestParseRequestWithInstl(t *testing.T) { - body := []byte(`{ - "max_key_length":20, - "user":{ - "gender":"F", - "buyeruid":"test_buyeruid", - "yob":2000, - "id":"testid" - }, - "prebid_version":"0.21.0-pre", - "sort_bids":1, - "ad_units":[ - { - "sizes":[ - { - "w":300, - "h":250 - } - ], - "bids": [ - { - "bidder": "ix" - }, - { - "bidder": "appnexus" - } - ], - "code":"5d748364ee9c46a2b112892fc3551b6f", - "instl": 1 - } - ], - "cache_markup":1, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.2" - }, - "sdk":{ - "version":"0.0.2", - "platform":"iOS", - "source":"prebid-mobile" - }, - "device":{ - "ifa":"test_device_ifa", - "osv":"9.3.5", - "os":"iOS", - "make":"Apple", - "model":"iPhone6,1" - }, - "tid":"abcd", - "account_id":"aecd6ef7-b992-4e99-9bb8-65e2d984e1dd" - } - `) - r := httptest.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - d, _ := dummycache.New() - hcc := config.HostCookie{} - - pbs_req, err := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err != nil { - t.Fatalf("Parse simple request failed: %v", err) - } - if len(pbs_req.Bidders) != 2 { - t.Errorf("Should have 2 bidders. ") - } - if pbs_req.Bidders[0].AdUnits[0].Instl != 1 { - t.Errorf("Parse instl failed.") - } - if pbs_req.Bidders[1].AdUnits[0].Instl != 1 { - t.Errorf("Parse instl failed.") - } - -} - -func TestTimeouts(t *testing.T) { - doTimeoutTest(t, 10, 15, 10, 0) - doTimeoutTest(t, 10, 0, 10, 0) - doTimeoutTest(t, 5, 5, 10, 0) - doTimeoutTest(t, 15, 15, 0, 0) - doTimeoutTest(t, 15, 0, 20, 15) -} - -func doTimeoutTest(t *testing.T, expected int, requested int, max uint64, def uint64) { - t.Helper() - cfg := &config.AuctionTimeouts{ - Default: def, - Max: max, - } - body := fmt.Sprintf(`{ - "tid": "abcd", - "timeout_millis": %d, - "app":{ - "bundle":"AppNexus.PrebidMobileDemo", - "ver":"0.0.2" - }, - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bids": [ - { - "bidder": "ix" - } - ] - } - ] -}`, requested) - r := httptest.NewRequest("POST", "/auction", strings.NewReader(body)) - d, _ := dummycache.New() - parsed, err := ParsePBSRequest(r, cfg, d, &config.HostCookie{}) - if err != nil { - t.Fatalf("Unexpected err: %v", err) - } - if parsed.TimeoutMillis != int64(expected) { - t.Errorf("Expected %dms timeout, got %dms", expected, parsed.TimeoutMillis) - } -} - -func TestParsePBSRequestUsesHostCookie(t *testing.T) { - body := []byte(`{ - "tid": "abcd", - "ad_units": [ - { - "code": "first", - "sizes": [{"w": 300, "h": 250}], - "bidders": [ - { - "bidder": "bidder1", - "params": { - "id": "417", - "siteID": "test-site" - } - } - ] - } - ] - } - `) - r, err := http.NewRequest("POST", "/auction", bytes.NewBuffer(body)) - r.Header.Add("Referer", "http://nytimes.com/cool.html") - if err != nil { - t.Fatalf("new request failed") - } - r.AddCookie(&http.Cookie{Name: "key", Value: "testcookie"}) - d, _ := dummycache.New() - hcc := config.HostCookie{ - CookieName: "key", - Family: "family", - OptOutCookie: config.Cookie{ - Name: "trp_optout", - Value: "true", - }, - } - - pbs_req, err2 := ParsePBSRequest(r, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, d, &hcc) - if err2 != nil { - t.Fatalf("Parse simple request failed %v", err2) - } - if uid, _, _ := pbs_req.Cookie.GetUID("family"); uid != "testcookie" { - t.Errorf("Failed to leverage host cookie space for user identifier") - } -} diff --git a/pbs/pbsresponse.go b/pbs/pbsresponse.go deleted file mode 100644 index b8cf2c19ff7..00000000000 --- a/pbs/pbsresponse.go +++ /dev/null @@ -1,84 +0,0 @@ -package pbs - -// PBSBid is a bid from the auction. These are produced by Adapters, and target a particular Ad Unit. -// -// This JSON format is a contract with both Prebid.js and Prebid-mobile. -// All changes *must* be backwards compatible, since clients cannot be forced to update their code. -type PBSBid struct { - // BidID identifies the Bid Request within the Ad Unit which this Bid targets. It should match one of - // the values inside PBSRequest.AdUnits[i].Bids[j].BidID. - BidID string `json:"bid_id"` - // AdUnitCode identifies the AdUnit which this Bid targets. - // It should match one of PBSRequest.AdUnits[i].Code, where "i" matches the AdUnit used in - // as BidID. - AdUnitCode string `json:"code"` - // Creative_id uniquely identifies the creative being served. It is not used by prebid-server, but - // it helps publishers and bidders identify and communicate about malicious or inappropriate ads. - // This project simply passes it along with the bid. - Creative_id string `json:"creative_id,omitempty"` - // CreativeMediaType shows whether the creative is a video or banner. - CreativeMediaType string `json:"media_type,omitempty"` - // BidderCode is the PBSBidder.BidderCode of the PBSBidder who made this bid. - BidderCode string `json:"bidder"` - // BidHash is the hash of the bidder's unique bid identifier for blockchain. It should not be sent to browser. - BidHash string `json:"-"` - // Price is the cpm, in US Dollars, which the bidder is willing to pay if this bid is chosen. - // TODO: Add support for other currencies someday. - Price float64 `json:"price"` - // NURL is a URL which returns ad markup, and should be called if the bid wins. - // If NURL and Adm are both defined, then Adm takes precedence. - NURL string `json:"nurl,omitempty"` - // Adm is the ad markup which should be used to deliver the ad, if this bid is chosen. - // If NURL and Adm are both defined, then Adm takes precedence. - Adm string `json:"adm,omitempty"` - // Width is the intended width which Adm should be shown, in pixels. - Width int64 `json:"width,omitempty"` - // Height is the intended width which Adm should be shown, in pixels. - Height int64 `json:"height,omitempty"` - // DealId is not used by prebid-server, but may be used by buyers and sellers who make special - // deals with each other. We simply pass this information along with the bid. - DealId string `json:"deal_id,omitempty"` - // CacheId is an ID in prebid-cache which can be used to fetch this ad's content. - // This supports prebid-mobile, which requires that the content be available from a URL. - CacheID string `json:"cache_id,omitempty"` - // Complete cache url returned from the prebid-cache. - // more flexible than a design that assumes the UUID is always appended to the end of the URL. - CacheURL string `json:"cache_url,omitempty"` - // ResponseTime is the number of milliseconds it took for the adapter to return a bid. - ResponseTime int `json:"response_time_ms,omitempty"` - AdServerTargeting map[string]string `json:"ad_server_targeting,omitempty"` -} - -// PBSBidSlice attaches the methods of sort.Interface to []PBSBid, ordering them by price. -// If two prices are equal, then the response time will be used as a tiebreaker. -// For more information, see https://golang.org/pkg/sort/#Interface -type PBSBidSlice []*PBSBid - -func (bids PBSBidSlice) Len() int { - return len(bids) -} - -func (bids PBSBidSlice) Less(i, j int) bool { - bidiResponseTimeInTerras := (float64(bids[i].ResponseTime) / 1000000000.0) - bidjResponseTimeInTerras := (float64(bids[j].ResponseTime) / 1000000000.0) - return bids[i].Price-bidiResponseTimeInTerras > bids[j].Price-bidjResponseTimeInTerras -} - -func (bids PBSBidSlice) Swap(i, j int) { - bids[i], bids[j] = bids[j], bids[i] -} - -type BidderDebug struct { - RequestURI string `json:"request_uri,omitempty"` - RequestBody string `json:"request_body,omitempty"` - ResponseBody string `json:"response_body,omitempty"` - StatusCode int `json:"status_code,omitempty"` -} - -type PBSResponse struct { - TID string `json:"tid,omitempty"` - Status string `json:"status,omitempty"` - BidderStatus []*PBSBidder `json:"bidder_status,omitempty"` - Bids PBSBidSlice `json:"bids,omitempty"` - BUrl string `json:"burl,omitempty"` -} diff --git a/pbs/pbsresponse_test.go b/pbs/pbsresponse_test.go deleted file mode 100644 index 0e51120cdf4..00000000000 --- a/pbs/pbsresponse_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package pbs - -import ( - "sort" - "testing" -) - -func TestSortBids(t *testing.T) { - bid1 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 0.0, - } - bid2 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 4.0, - } - bid3 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 2.0, - } - bid4 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 0.50, - } - - bids := make(PBSBidSlice, 0) - bids = append(bids, &bid1, &bid2, &bid3, &bid4) - - sort.Sort(bids) - if bids[0].Price != 4.0 { - t.Error("Expected 4.00 to be highest price") - } - if bids[1].Price != 2.0 { - t.Error("Expected 2.00 to be second highest price") - } - if bids[2].Price != 0.5 { - t.Error("Expected 0.50 to be third highest price") - } - if bids[3].Price != 0.0 { - t.Error("Expected 0.00 to be lowest price") - } -} - -func TestSortBidsWithResponseTimes(t *testing.T) { - bid1 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 1.0, - ResponseTime: 70, - } - bid2 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 1.0, - ResponseTime: 20, - } - bid3 := PBSBid{ - BidID: "testBidId", - AdUnitCode: "testAdUnitCode", - BidderCode: "testBidderCode", - Price: 1.0, - ResponseTime: 99, - } - - bids := make(PBSBidSlice, 0) - bids = append(bids, &bid1, &bid2, &bid3) - - sort.Sort(bids) - if bids[0] != &bid2 { - t.Error("Expected bid 2 to win") - } - if bids[1] != &bid1 { - t.Error("Expected bid 1 to be second") - } - if bids[2] != &bid3 { - t.Error("Expected bid 3 to be last") - } -} diff --git a/pbs/usersync.go b/pbs/usersync.go index 85b55f42aeb..d5043b8c13f 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -7,13 +7,10 @@ import ( "net/http" "net/url" "strings" - "time" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/server/ssl" "github.com/prebid/prebid-server/usersync" ) @@ -21,27 +18,10 @@ import ( // Recaptcha code from https://github.com/haisum/recaptcha/blob/master/recaptcha.go const RECAPTCHA_URL = "https://www.google.com/recaptcha/api/siteverify" -const ( - USERSYNC_OPT_OUT = "usersync.opt_outs" - USERSYNC_BAD_REQUEST = "usersync.bad_requests" - USERSYNC_SUCCESS = "usersync.%s.sets" -) - -// uidWithExpiry bundles the UID with an Expiration date. -// After the expiration, the UID is no longer valid. -type uidWithExpiry struct { - // UID is the ID given to a user by a particular bidder - UID string `json:"uid"` - // Expires is the time at which this UID should no longer apply. - Expires time.Time `json:"expires"` -} - type UserSyncDeps struct { ExternalUrl string RecaptchaSecret string HostCookieConfig *config.HostCookie - MetricsEngine metrics.MetricsEngine - PBSAnalytics analytics.PBSAnalyticsModule } // Struct for parsing json in google's response @@ -79,7 +59,7 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr rr := r.FormValue("g-recaptcha-response") if rr == "" { - http.Redirect(w, r, fmt.Sprintf("%s/static/optout.html", deps.ExternalUrl), 301) + http.Redirect(w, r, fmt.Sprintf("%s/static/optout.html", deps.ExternalUrl), http.StatusMovedPermanently) return } @@ -98,8 +78,8 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr pc.SetCookieOnResponse(w, false, deps.HostCookieConfig, deps.HostCookieConfig.TTLDuration()) if optout == "" { - http.Redirect(w, r, deps.HostCookieConfig.OptInURL, 301) + http.Redirect(w, r, deps.HostCookieConfig.OptInURL, http.StatusMovedPermanently) } else { - http.Redirect(w, r, deps.HostCookieConfig.OptOutURL, 301) + http.Redirect(w, r, deps.HostCookieConfig.OptOutURL, http.StatusMovedPermanently) } } diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go index 730d54b0acb..a24a139ea1d 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -120,7 +120,7 @@ func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []s responseBody, err := ioutil.ReadAll(anResp.Body) if anResp.StatusCode != 200 { - logError(&errs, "Prebid Cache call to %s returned %d: %s", putURL, anResp.StatusCode, responseBody) + logError(&errs, "Prebid Cache call to %s returned %d: %s", c.putUrl, anResp.StatusCode, responseBody) return uuidsToReturn, errs } diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index 60237bbbb27..ec390364849 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -280,10 +280,18 @@ func assertStringEqual(t *testing.T, expected, actual string) { } } +type handlerResponseObject struct { + UUID string `json:"uuid"` +} + +type handlerResponse struct { + Responses []handlerResponseObject `json:"responses"` +} + func newHandler(numResponses int) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := response{ - Responses: make([]responseObject, numResponses), + resp := handlerResponse{ + Responses: make([]handlerResponseObject, numResponses), } for i := 0; i < numResponses; i++ { resp.Responses[i].UUID = strconv.Itoa(i) diff --git a/prebid_cache_client/prebid_cache.go b/prebid_cache_client/prebid_cache.go deleted file mode 100644 index cde7ec8d951..00000000000 --- a/prebid_cache_client/prebid_cache.go +++ /dev/null @@ -1,122 +0,0 @@ -package prebid_cache_client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - - "golang.org/x/net/context/ctxhttp" -) - -// This file is deprecated, and is only used to cache things for the legacy (/auction) endpoint. -// For /openrtb2/auction cache, see client.go in this package. - -type CacheObject struct { - Value interface{} - UUID string - IsVideo bool -} - -type BidCache struct { - Adm string `json:"adm,omitempty"` - NURL string `json:"nurl,omitempty"` - Width int64 `json:"width,omitempty"` - Height int64 `json:"height,omitempty"` -} - -// internal protocol objects -type putObject struct { - Type string `json:"type"` - Value interface{} `json:"value"` -} - -type putRequest struct { - Puts []putObject `json:"puts"` -} - -type responseObject struct { - UUID string `json:"uuid"` -} -type response struct { - Responses []responseObject `json:"responses"` -} - -var ( - client *http.Client - baseURL string - putURL string -) - -// InitPrebidCache setup the global prebid cache -func InitPrebidCache(baseurl string) { - baseURL = baseurl - putURL = fmt.Sprintf("%s/cache", baseURL) - - ts := &http.Transport{ - MaxIdleConns: 10, - IdleConnTimeout: 65, - } - - client = &http.Client{ - Transport: ts, - } -} - -// Put will send the array of objs and update each with a UUID -func Put(ctx context.Context, objs []*CacheObject) error { - // Fixes #197 - if len(objs) == 0 { - return nil - } - pr := putRequest{Puts: make([]putObject, len(objs))} - for i, obj := range objs { - if obj.IsVideo { - pr.Puts[i].Type = "xml" - } else { - pr.Puts[i].Type = "json" - } - pr.Puts[i].Value = obj.Value - } - // Don't want to escape the HTML for adm and nurl - buf := new(bytes.Buffer) - enc := json.NewEncoder(buf) - enc.SetEscapeHTML(false) - err := enc.Encode(pr) - if err != nil { - return err - } - - httpReq, err := http.NewRequest("POST", putURL, buf) - if err != nil { - return err - } - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - anResp, err := ctxhttp.Do(ctx, client, httpReq) - if err != nil { - return err - } - defer anResp.Body.Close() - - if anResp.StatusCode != 200 { - return fmt.Errorf("HTTP status code %d", anResp.StatusCode) - } - - var resp response - if err := json.NewDecoder(anResp.Body).Decode(&resp); err != nil { - return err - } - - if len(resp.Responses) != len(objs) { - return fmt.Errorf("Put response length didn't match") - } - - for i, r := range resp.Responses { - objs[i].UUID = r.UUID - } - - return nil -} diff --git a/prebid_cache_client/prebid_cache_test.go b/prebid_cache_client/prebid_cache_test.go deleted file mode 100644 index 65688789fd0..00000000000 --- a/prebid_cache_client/prebid_cache_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package prebid_cache_client - -import ( - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "fmt" -) - -var delay time.Duration -var ( - MaxValueLength = 1024 * 10 - MaxNumValues = 10 -) - -type putAnyObject struct { - Type string `json:"type"` - Value json.RawMessage `json:"value"` -} - -type putAnyRequest struct { - Puts []putAnyObject `json:"puts"` -} - -func DummyPrebidCacheServer(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read the request body.", http.StatusBadRequest) - return - } - defer r.Body.Close() - var put putAnyRequest - - err = json.Unmarshal(body, &put) - if err != nil { - http.Error(w, "Request body "+string(body)+" is not valid JSON.", http.StatusBadRequest) - return - } - - if len(put.Puts) > MaxNumValues { - http.Error(w, fmt.Sprintf("More keys than allowed: %d", MaxNumValues), http.StatusBadRequest) - return - } - - resp := response{ - Responses: make([]responseObject, len(put.Puts)), - } - for i, p := range put.Puts { - resp.Responses[i].UUID = fmt.Sprintf("UUID-%d", i+1) // deterministic for testing - if len(p.Value) > MaxValueLength { - http.Error(w, fmt.Sprintf("Value is larger than allowed size: %d", MaxValueLength), http.StatusBadRequest) - return - } - if len(p.Value) == 0 { - http.Error(w, "Missing value.", http.StatusBadRequest) - return - } - if p.Type != "xml" && p.Type != "json" { - http.Error(w, fmt.Sprintf("Type must be one of [\"json\", \"xml\"]. Found %v", p.Type), http.StatusBadRequest) - return - } - } - - bytes, err := json.Marshal(&resp) - if err != nil { - http.Error(w, "Failed to serialize UUIDs into JSON.", http.StatusInternalServerError) - return - } - if delay > 0 { - <-time.After(delay) - } - w.Header().Set("Content-Type", "application/json") - w.Write(bytes) -} - -func TestPrebidClient(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer)) - defer server.Close() - - cobj := make([]*CacheObject, 3) - - // example bids - cobj[0] = &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\"}", - NURL: "https://www.facebook.com/audiencenetwork/nurl/?partner=442648859414574&app=1995257847363113&placement=1997038003851764&auction=d3013e9e-ca55-4a86-9baa-d44e31355e1d&impression=bannerad1&request=7187783259538616534&bid=3832427901228167009&ortb_loss_code=0", - Width: 300, - Height: 250, - }, - } - cobj[1] = &CacheObject{ - IsVideo: false, - Value: &BidCache{ - Adm: "", - Width: 300, - Height: 250, - }, - } - cobj[2] = &CacheObject{ - IsVideo: true, - Value: "", - } - InitPrebidCache(server.URL) - - ctx := context.TODO() - err := Put(ctx, cobj) - if err != nil { - t.Fatalf("pbc put failed: %v", err) - } - - if cobj[0].UUID != "UUID-1" { - t.Errorf("First object UUID was '%s', should have been 'UUID-1'", cobj[0].UUID) - } - if cobj[1].UUID != "UUID-2" { - t.Errorf("Second object UUID was '%s', should have been 'UUID-2'", cobj[1].UUID) - } - if cobj[2].UUID != "UUID-3" { - t.Errorf("Third object UUID was '%s', should have been 'UUID-3'", cobj[2].UUID) - } - - delay = 5 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - err = Put(ctx, cobj) - if err == nil { - t.Fatalf("pbc put succeeded but should have timed out") - } -} - -// Prevents #197 -func TestEmptyBids(t *testing.T) { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Errorf("The server should not be called.") - }) - server := httptest.NewServer(handler) - defer server.Close() - - InitPrebidCache(server.URL) - - if err := Put(context.Background(), []*CacheObject{}); err != nil { - t.Errorf("Error on Put: %v", err) - } -} diff --git a/privacy/ccpa/parsedpolicy_test.go b/privacy/ccpa/parsedpolicy_test.go index 33563b50567..4bbb4cbfd0f 100644 --- a/privacy/ccpa/parsedpolicy_test.go +++ b/privacy/ccpa/parsedpolicy_test.go @@ -3,9 +3,7 @@ package ccpa import ( "testing" - "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func TestValidateConsent(t *testing.T) { @@ -380,12 +378,3 @@ func TestShouldEnforce(t *testing.T) { assert.Equal(t, test.expected, result, test.description) } } - -type mockPolicWriter struct { - mock.Mock -} - -func (m *mockPolicWriter) Write(req *openrtb2.BidRequest) error { - args := m.Called(req) - return args.Error(0) -} diff --git a/router/router.go b/router/router.go index 90074753a5b..81623f13838 100644 --- a/router/router.go +++ b/router/router.go @@ -3,7 +3,6 @@ package router import ( "context" "crypto/tls" - "database/sql" "encoding/json" "fmt" "io/ioutil" @@ -12,34 +11,17 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/currency" - "github.com/prebid/prebid-server/endpoints/events" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/util/uuidutil" - "github.com/prebid/prebid-server/version" - - "github.com/prebid/prebid-server/metrics" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adform" - "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/pubmatic" - "github.com/prebid/prebid-server/adapters/pulsepoint" - "github.com/prebid/prebid-server/adapters/rubicon" - "github.com/prebid/prebid-server/adapters/sovrn" analyticsConf "github.com/prebid/prebid-server/analytics/config" - "github.com/prebid/prebid-server/cache" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/cache/filecache" - "github.com/prebid/prebid-server/cache/postgrescache" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/endpoints" + "github.com/prebid/prebid-server/endpoints/events" infoEndpoints "github.com/prebid/prebid-server/endpoints/info" "github.com/prebid/prebid-server/endpoints/openrtb2" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/exchange" "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/metrics" metricsConf "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/pbs" @@ -49,6 +31,8 @@ import ( storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" "github.com/prebid/prebid-server/usersync" "github.com/prebid/prebid-server/util/sliceutil" + "github.com/prebid/prebid-server/util/uuidutil" + "github.com/prebid/prebid-server/version" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -56,9 +40,6 @@ import ( "github.com/rs/cors" ) -var dataCache cache.Cache -var exchanges map[string]adapters.Adapter - // NewJsonDirectoryServer is used to serve .json files from a directory as a single blob. For example, // given a directory containing the files "a.json" and "b.json", this returns a Handle which serves JSON like: // @@ -123,51 +104,6 @@ func (m NoCache) ServeHTTP(w http.ResponseWriter, r *http.Request) { m.Handler.ServeHTTP(w, r) } -func loadDataCache(cfg *config.Configuration, db *sql.DB) (err error) { - switch cfg.DataCache.Type { - case "dummy": - dataCache, err = dummycache.New() - if err != nil { - glog.Fatalf("Dummy cache not configured: %s", err.Error()) - } - - case "postgres": - if db == nil { - return fmt.Errorf("Nil db cannot connect to postgres. Did you forget to set the config.stored_requests.postgres values?") - } - dataCache = postgrescache.New(db, postgrescache.CacheConfig{ - Size: cfg.DataCache.CacheSize, - TTL: cfg.DataCache.TTLSeconds, - }) - return nil - case "filecache": - dataCache, err = filecache.New(cfg.DataCache.Filename) - if err != nil { - return fmt.Errorf("FileCache Error: %s", err.Error()) - } - - default: - return fmt.Errorf("Unknown datacache.type: %s", cfg.DataCache.Type) - } - return nil -} - -func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter { - // These keys _must_ coincide with the bidder code in Prebid.js, if the adapter exists in both projects - return map[string]adapters.Adapter{ - "appnexus": appnexus.NewAppNexusLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), - "districtm": appnexus.NewAppNexusLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderAppnexus)].PlatformID), - "ix": ix.NewIxLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint), - "pubmatic": pubmatic.NewPubmaticLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), - "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), - "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), - } -} - type Router struct { *httprouter.Router MetricsEngine *metricsConf.DetailedMetricsEngine @@ -211,10 +147,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R }, } - // Hack because of how legacy handles districtm - legacyBidderList := openrtb_ext.CoreBidderNames() - legacyBidderList = append(legacyBidderList, openrtb_ext.BidderName("districtm")) - p, _ := filepath.Abs(infoDirectory) bidderInfos, err := config.LoadBidderInfoFromDisk(p, cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) if err != nil { @@ -244,13 +176,10 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } // Metrics engine - r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, legacyBidderList, syncerKeys) - db, shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) + r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.CoreBidderNames(), syncerKeys) + shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router) // todo(zachbadgett): better shutdown r.Shutdown = shutdown - if err := loadDataCache(cfg, db); err != nil { - return nil, fmt.Errorf("Prebid Server could not load data cache: %v", err) - } pbsAnalytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics) @@ -270,7 +199,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R gvlVendorIDs := bidderInfos.ToGVLVendorIDMap() gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, gvlVendorIDs, generalHttpClient) - exchanges = newExchangeMap(cfg) cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine) adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, bidderInfos, r.MetricsEngine) @@ -301,10 +229,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, metrics.ReqTypeVideo) } - if cfg.EnableLegacyAuction { - r.POST("/auction", endpoints.Auction(cfg, syncersByBidder, gdprPerms, r.MetricsEngine, dataCache, exchanges)) - } - r.POST("/openrtb2/auction", openrtbEndpoint) r.POST("/openrtb2/video", videoEndpoint) r.GET("/openrtb2/amp", ampEndpoint) @@ -331,8 +255,6 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R HostCookieConfig: &(cfg.HostCookie), ExternalUrl: cfg.ExternalURL, RecaptchaSecret: cfg.RecaptchaSecret, - MetricsEngine: r.MetricsEngine, - PBSAnalytics: pbsAnalytics, } r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg.HostCookie, syncersByBidder, gdprPerms, pbsAnalytics, r.MetricsEngine)) diff --git a/router/router_test.go b/router/router_test.go index 24a7709c365..b4ceaff16a9 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "net/http" "net/http/httptest" - "os" "testing" "github.com/prebid/prebid-server/config" @@ -62,18 +61,6 @@ func TestNewJsonDirectoryServer(t *testing.T) { ensureHasKey(t, data, "aliastest") } -func TestExchangeMap(t *testing.T) { - exchanges := newExchangeMap(&config.Configuration{}) - bidderMap := openrtb_ext.BuildBidderMap() - for bidderName := range exchanges { - // OpenRTB doesn't support hardcoded aliases... so this test skips districtm, - // which was the only alias in the legacy adapter map. - if _, ok := bidderMap[bidderName]; bidderName != "districtm" && !ok { - t.Errorf("Bidder %s exists in exchange, but is not a part of the BidderMap.", bidderName) - } - } -} - func TestApplyBidderInfoConfigOverrides(t *testing.T) { var testCases = []struct { description string @@ -298,38 +285,6 @@ func TestNoCache(t *testing.T) { } } -func TestLoadDataCache(t *testing.T) { - // Test dummy - if err := loadDataCache(&config.Configuration{ - DataCache: config.DataCache{ - Type: "dummy", - }, - }, nil); err != nil { - t.Errorf("data cache: dummy: %s", err) - } - // Test postgres error - if err := loadDataCache(&config.Configuration{ - DataCache: config.DataCache{ - Type: "postgres", - }, - }, nil); err == nil { - t.Errorf("data cache: postgres: db nil should return error") - } - // Test file - d, _ := ioutil.TempDir("", "pbs-filecache") - defer os.RemoveAll(d) - f, _ := ioutil.TempFile(d, "file") - defer f.Close() - if err := loadDataCache(&config.Configuration{ - DataCache: config.DataCache{ - Type: "filecache", - Filename: f.Name(), - }, - }, nil); err != nil { - t.Errorf("data cache: filecache: %s", err) - } -} - var testDefReqConfig = config.DefReqConfig{ Type: "file", FileSystem: config.DefReqFiles{ diff --git a/server/prometheus.go b/server/prometheus.go index 4b9f7037d0a..6f0a0b2df45 100644 --- a/server/prometheus.go +++ b/server/prometheus.go @@ -9,13 +9,10 @@ import ( "github.com/prebid/prebid-server/config" metricsconfig "github.com/prebid/prebid-server/metrics/config" - prometheusMetrics "github.com/prebid/prebid-server/metrics/prometheus" ) func newPrometheusServer(cfg *config.Configuration, metrics *metricsconfig.DetailedMetricsEngine) *http.Server { - var proMetrics *prometheusMetrics.Metrics - - proMetrics = metrics.PrometheusMetrics + proMetrics := metrics.PrometheusMetrics if proMetrics == nil { glog.Fatal("Prometheus metrics configured, but a Prometheus metrics engine was not found. Cannot set up a Prometheus listener.") diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index a145a3b43a2..7d23f942d56 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -32,7 +32,7 @@ func TestAccountFetcher(t *testing.T) { assertErrorCount(t, 0, errs) assert.JSONEq(t, `{"disabled":false, "id":"valid"}`, string(account)) - account, errs = fetcher.FetchAccount(context.Background(), "nonexistent") + _, errs = fetcher.FetchAccount(context.Background(), "nonexistent") assertErrorCount(t, 1, errs) assert.Error(t, errs[0]) assert.Equal(t, stored_requests.NotFoundError{"nonexistent", "Account"}, errs[0]) diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index bc12caecb98..75a92e3f331 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -74,7 +74,6 @@ func NewFetcher(client *http.Client, endpoint string) *HttpFetcher { type HttpFetcher struct { client *http.Client Endpoint string - hasQuery bool Categories map[string]map[string]stored_requests.Category } diff --git a/stored_requests/backends/http_fetcher/fetcher_test.go b/stored_requests/backends/http_fetcher/fetcher_test.go index 30933181e1d..10d3984a818 100644 --- a/stored_requests/backends/http_fetcher/fetcher_test.go +++ b/stored_requests/backends/http_fetcher/fetcher_test.go @@ -1,10 +1,8 @@ package http_fetcher import ( - "bytes" "context" "encoding/json" - "io" "net/http" "net/http/httptest" "strings" @@ -168,42 +166,6 @@ func TestErrResponse(t *testing.T) { assert.Len(t, errs, 1) } -func assertSameContents(t *testing.T, expected map[string]json.RawMessage, actual map[string]json.RawMessage) { - if len(expected) != len(actual) { - t.Errorf("Wrong counts. Expected %d, actual %d", len(expected), len(actual)) - return - } - for expectedKey, expectedVal := range expected { - if actualVal, ok := actual[expectedKey]; ok { - if !bytes.Equal(expectedVal, actualVal) { - t.Errorf("actual[%s] value %s does not match expected: %s", expectedKey, string(actualVal), string(actualVal)) - } - } else { - t.Errorf("actual map missing expected key %s", expectedKey) - } - } -} - -func assertSameErrMsgs(t *testing.T, expected []string, actual []error) { - if len(expected) != len(actual) { - t.Errorf("Wrong error counts. Expected %d, actual %d", len(expected), len(actual)) - return - } - for i, expectedErr := range expected { - if actual[i].Error() != expectedErr { - t.Errorf("Wrong error[%d]. Expected %s, got %s", i, expectedErr, actual[i].Error()) - } - } -} - -type closeWrapper struct { - io.Reader -} - -func (w closeWrapper) Close() error { - return nil -} - func newFetcherBrokenBackend() (fetcher *HttpFetcher, closer func()) { handler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) diff --git a/stored_requests/caches/nil_cache/nil_cache.go b/stored_requests/caches/nil_cache/nil_cache.go index d043ae55c96..88bbd404674 100644 --- a/stored_requests/caches/nil_cache/nil_cache.go +++ b/stored_requests/caches/nil_cache/nil_cache.go @@ -13,9 +13,7 @@ func (c *NilCache) Get(ctx context.Context, ids []string) map[string]json.RawMes } func (c *NilCache) Save(ctx context.Context, data map[string]json.RawMessage) { - return } func (c *NilCache) Invalidate(ctx context.Context, ids []string) { - return } diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index f682ff932f4..89022582ace 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -93,12 +93,12 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.Metr return } -// NewStoredRequests returns five things: +// NewStoredRequests returns: // -// 1. A DB connection, if one was created. This may be nil. -// 2. A function which should be called on shutdown for graceful cleanups. -// 3. A Fetcher which can be used to get Stored Requests for /openrtb2/auction -// 4. A Fetcher which can be used to get Stored Requests for /openrtb2/amp +// 1. A function which should be called on shutdown for graceful cleanups. +// 2. A Fetcher which can be used to get Stored Requests for /openrtb2/auction +// 3. A Fetcher which can be used to get Stored Requests for /openrtb2/amp +// 4. A Fetcher which can be used to get Account data // 5. A Fetcher which can be used to get Category Mapping data // 6. A Fetcher which can be used to get Stored Requests for /openrtb2/video // @@ -107,7 +107,7 @@ func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.Metr // // As a side-effect, it will add some endpoints to the router if the config calls for it. // In the future we should look for ways to simplify this so that it's not doing two things. -func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router) (db *sql.DB, shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { +func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router) (shutdown func(), fetcher stored_requests.Fetcher, ampFetcher stored_requests.Fetcher, accountsFetcher stored_requests.AccountFetcher, categoriesFetcher stored_requests.CategoryFetcher, videoFetcher stored_requests.Fetcher) { // TODO: Switch this to be set in config defaults //if cfg.CategoryMapping.CacheEvents.Enabled && cfg.CategoryMapping.CacheEvents.Endpoint == "" { // cfg.CategoryMapping.CacheEvents.Endpoint = "/storedrequest/categorymapping" @@ -121,8 +121,6 @@ func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsE fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, &dbc) fetcher5, shutdown5 := CreateStoredRequests(&cfg.Accounts, metricsEngine, client, router, &dbc) - db = dbc.db - fetcher = fetcher1.(stored_requests.Fetcher) ampFetcher = fetcher2.(stored_requests.Fetcher) categoriesFetcher = fetcher3.(stored_requests.CategoryFetcher) diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go index 5b89943572f..7cb8f4b9b6d 100644 --- a/stored_requests/events/events.go +++ b/stored_requests/events/events.go @@ -77,7 +77,7 @@ func (e *EventListener) Listen(cache stored_requests.Cache, events EventProducer e.onInvalidate() } case <-e.stop: - break + return } } } diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index 4792db1969f..4e87db5dd0a 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -33,7 +33,7 @@ func TestEmptyOptOutCookie(t *testing.T) { func TestEmptyCookie(t *testing.T) { cookie := &Cookie{ - uids: make(map[string]uidWithExpiry, 0), + uids: make(map[string]uidWithExpiry), optOut: false, birthday: timestamp(), } From f438793ef705fe00b4fc2dfd6ae33e46b02814e2 Mon Sep 17 00:00:00 2001 From: Michael Kuryshev Date: Thu, 21 Oct 2021 19:20:18 +0200 Subject: [PATCH 120/140] VIS.X: mediatype handling fix for complex requests (#2040) --- adapters/visx/visx.go | 46 +++- .../exemplary/multitype-banner-response.json | 199 ++++++++++++++++++ .../exemplary/multitype-video-response.json | 199 ++++++++++++++++++ 3 files changed, 433 insertions(+), 11 deletions(-) create mode 100644 adapters/visx/visxtest/exemplary/multitype-banner-response.json create mode 100644 adapters/visx/visxtest/exemplary/multitype-video-response.json diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index f0b25b4a2a3..53277ff8fe4 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -16,16 +16,29 @@ type VisxAdapter struct { endpoint string } +type visxBidExtPrebidMeta struct { + MediaType openrtb_ext.BidType `json:"mediaType"` +} + +type visxBidExtPrebid struct { + Meta visxBidExtPrebidMeta `json:"meta"` +} + +type visxBidExt struct { + Prebid visxBidExtPrebid `json:"prebid"` +} + type visxBid struct { - ImpID string `json:"impid"` - Price float64 `json:"price"` - UID int `json:"auid"` - CrID string `json:"crid,omitempty"` - AdM string `json:"adm,omitempty"` - ADomain []string `json:"adomain,omitempty"` - DealID string `json:"dealid,omitempty"` - W uint64 `json:"w,omitempty"` - H uint64 `json:"h,omitempty"` + ImpID string `json:"impid"` + Price float64 `json:"price"` + UID int `json:"auid"` + CrID string `json:"crid,omitempty"` + AdM string `json:"adm,omitempty"` + ADomain []string `json:"adomain,omitempty"` + DealID string `json:"dealid,omitempty"` + W uint64 `json:"w,omitempty"` + H uint64 `json:"h,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` } type visxSeatBid struct { @@ -101,8 +114,9 @@ func (a *VisxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq bid.H = int64(sb.Bid[i].H) bid.ADomain = sb.Bid[i].ADomain bid.DealID = sb.Bid[i].DealID + bid.Ext = sb.Bid[i].Ext - bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp, sb.Bid[i]) if err != nil { return nil, []error{err} } @@ -117,9 +131,19 @@ func (a *VisxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq } -func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp, bid visxBid) (openrtb_ext.BidType, error) { for _, imp := range imps { if imp.ID == impID { + var ext visxBidExt + if err := json.Unmarshal(bid.Ext, &ext); err == nil { + if ext.Prebid.Meta.MediaType == openrtb_ext.BidTypeBanner { + return openrtb_ext.BidTypeBanner, nil + } + if ext.Prebid.Meta.MediaType == openrtb_ext.BidTypeVideo { + return openrtb_ext.BidTypeVideo, nil + } + } + if imp.Banner != nil { return openrtb_ext.BidTypeBanner, nil } diff --git a/adapters/visx/visxtest/exemplary/multitype-banner-response.json b/adapters/visx/visxtest/exemplary/multitype-banner-response.json new file mode 100644 index 00000000000..0ef214dd372 --- /dev/null +++ b/adapters/visx/visxtest/exemplary/multitype-banner-response.json @@ -0,0 +1,199 @@ +{ + "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", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "cur": ["USD"], + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": ["USD"], + "seatbid": [ + { + "bid": [ + { + "crid": "2_260", + "price": 0.500000, + "adm": "some-test-ad", + "impid": "test-imp-id", + "auid": 11, + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "w": 300, + "ext": { + "prebid": { + "meta": { + "mediaType": "banner" + } + } + } + } + ], + "seat": "51" + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "price": 0.5, + "adm": "some-test-ad", + "impid": "test-imp-id", + "id": "test-request-id", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "crid": "2_260", + "w": 300, + "ext": { + "prebid": { + "meta": { + "mediaType": "banner" + } + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/visx/visxtest/exemplary/multitype-video-response.json b/adapters/visx/visxtest/exemplary/multitype-video-response.json new file mode 100644 index 00000000000..17877315fcf --- /dev/null +++ b/adapters/visx/visxtest/exemplary/multitype-video-response.json @@ -0,0 +1,199 @@ +{ + "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", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://localhost/prebid", + "body": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "cur": ["USD"], + "imp": [{ + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 300, + "h": 250 + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "secure": 1, + "ext": { + "bidder": { + "size": [ + 300, + 250 + ], + "uid": 11 + } + } + }], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375, + "ext": { + "prebid": { + "interstitial": { + "minwidthperc": 50, + "minheightperc": 40 + } + } + } + }, + "at": 1, + "tmax": 2000, + "source": { + "tid": "283746293874293" + }, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus", + "emxdigital": "appnexus" + } + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": ["USD"], + "seatbid": [ + { + "bid": [ + { + "crid": "2_260", + "price": 0.500000, + "adm": "some-test-ad-vast", + "impid": "test-imp-id", + "auid": 11, + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "w": 300, + "ext": { + "prebid": { + "meta": { + "mediaType": "video" + } + } + } + } + ], + "seat": "51" + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "price": 0.5, + "adm": "some-test-ad-vast", + "impid": "test-imp-id", + "id": "test-request-id", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "dealid": "test_deal_id", + "crid": "2_260", + "w": 300, + "ext": { + "prebid": { + "meta": { + "mediaType": "video" + } + } + } + }, + "type": "video" + }] + }] +} From fe51b833b77a792d6b9641b60c8c6bd943e6bf84 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 21 Oct 2021 19:23:24 +0200 Subject: [PATCH 121/140] Update impactify.yaml (#2053) --- static/bidder-info/impactify.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/bidder-info/impactify.yaml b/static/bidder-info/impactify.yaml index b0316d7acae..c0ca61ae4b6 100644 --- a/static/bidder-info/impactify.yaml +++ b/static/bidder-info/impactify.yaml @@ -1,5 +1,5 @@ maintainer: - email: "contact@impactify.io" + email: "support@impactify.io" gvlVendorID: 606 capabilities: site: From d7d4b8b14c76f148b53cab9349b9c1dcb63ff372 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Fri, 22 Oct 2021 04:47:57 +0200 Subject: [PATCH 122/140] New Adapter: RichAudience (#2001) Co-authored-by: sgimenez --- adapters/richaudience/params_test.go | 49 +++++ adapters/richaudience/richaudience.go | 202 ++++++++++++++++++ adapters/richaudience/richaudience_test.go | 161 ++++++++++++++ .../exemplary/single-banner-app.json | 122 +++++++++++ .../single-banner-defaultCurrency.json | 129 +++++++++++ .../exemplary/single-banner-deviceConfig.json | 132 ++++++++++++ .../exemplary/single-banner-extUser.json | 162 ++++++++++++++ .../exemplary/single-banner-floorPrice.json | 132 ++++++++++++ .../exemplary/single-banner-iPv6.json | 130 +++++++++++ .../exemplary/single-banner-nosecure.json | 130 +++++++++++ .../exemplary/single-banner-setCurrency.json | 131 ++++++++++++ .../exemplary/single-banner-sitePage.json | 130 +++++++++++ .../exemplary/single-banner.json | 130 +++++++++++ .../supplemental/formatNotFound.json | 39 ++++ .../supplemental/invalidParams.json | 38 ++++ .../supplemental/noIPDevice.json | 42 ++++ .../supplemental/notFoundParams.json | 36 ++++ .../supplemental/responseBlank.json | 90 ++++++++ .../supplemental/statusCode400.json | 93 ++++++++ .../supplemental/statusCodeError.json | 93 ++++++++ .../supplemental/unexpectedStatusCode.json | 93 ++++++++ config/config.go | 1 + exchange/adapter_builders.go | 4 +- exchange/bidder.go | 1 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_richaudience.go | 9 + static/bidder-info/richaudience.yaml | 15 ++ static/bidder-params/richaudience.json | 31 +++ 28 files changed, 2326 insertions(+), 1 deletion(-) create mode 100644 adapters/richaudience/params_test.go create mode 100644 adapters/richaudience/richaudience.go create mode 100644 adapters/richaudience/richaudience_test.go create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner-app.json create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner-defaultCurrency.json create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner-deviceConfig.json create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner-extUser.json create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner-floorPrice.json create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner-iPv6.json create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner-nosecure.json create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner-setCurrency.json create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner-sitePage.json create mode 100644 adapters/richaudience/richaudiencetest/exemplary/single-banner.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/formatNotFound.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/invalidParams.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/noIPDevice.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/notFoundParams.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/responseBlank.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/statusCode400.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/statusCodeError.json create mode 100644 adapters/richaudience/richaudiencetest/supplemental/unexpectedStatusCode.json create mode 100644 openrtb_ext/imp_richaudience.go create mode 100644 static/bidder-info/richaudience.yaml create mode 100644 static/bidder-params/richaudience.json diff --git a/adapters/richaudience/params_test.go b/adapters/richaudience/params_test.go new file mode 100644 index 00000000000..038936f3cbf --- /dev/null +++ b/adapters/richaudience/params_test.go @@ -0,0 +1,49 @@ +package richaudience + +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 schema. %v", err) + } + + for _, p := range validParams { + if err := validator.Validate(openrtb_ext.BidderRichaudience, json.RawMessage(p)); err != nil { + t.Errorf("Schema rejected valid params: %s", p) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json schema. %v", err) + } + + for _, p := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderRichaudience, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"pid":"hash", "supplyType":"site"}`, + `{"pid":"hash", "supplyType":"site", "test": true}`, +} + +var invalidParams = []string{ + `{"pid": 42}`, + `{"pid": "", "supplyType":0}`, + `{"pid": 11, "supplyType":"site"}`, + `{"pid": "hash", "supplyType":11}`, + `{"pid": "hash"}`, + `{"supplyType":"site"}`, + `{}`, +} diff --git a/adapters/richaudience/richaudience.go b/adapters/richaudience/richaudience.go new file mode 100644 index 00000000000..333df56408b --- /dev/null +++ b/adapters/richaudience/richaudience.go @@ -0,0 +1,202 @@ +package richaudience + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "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 RichAudience 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) { + raiHeaders := http.Header{} + + setHeaders(&raiHeaders) + + isUrlSecure := getIsUrlSecure(request) + + resImps, err := setImp(request, isUrlSecure) + if err != nil { + return nil, []error{err} + } + + request.Imp = resImps + + if err = validateDevice(request); err != nil { + return nil, []error{err} + } + + req, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: req, + Headers: raiHeaders, + } + + 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 bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &bidResp); err != nil { + + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + 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 { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: openrtb_ext.BidTypeBanner, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, nil +} + +func setHeaders(raiHeaders *http.Header) { + raiHeaders.Set("Content-Type", "application/json;charset=utf-8") + raiHeaders.Set("Accept", "application/json") + raiHeaders.Add("X-Openrtb-Version", "2.5") +} + +func setImp(request *openrtb2.BidRequest, isUrlSecure bool) (resImps []openrtb2.Imp, err error) { + for _, imp := range request.Imp { + var secure = int8(0) + raiExt, errImp := parseImpExt(&imp) + if errImp != nil { + return nil, errImp + } + + if raiExt != nil { + if raiExt.Pid != "" { + imp.TagID = raiExt.Pid + } + + if raiExt.Test { + request.Test = int8(1) + } + + if raiExt.BidFloorCur != "" { + imp.BidFloorCur = raiExt.BidFloorCur + } else if imp.BidFloorCur == "" { + imp.BidFloorCur = "USD" + } + } + if isUrlSecure { + secure = int8(1) + } + + imp.Secure = &secure + + if imp.Banner.W == nil && imp.Banner.H == nil { + if len(imp.Banner.Format) == 0 { + err = &errortypes.BadInput{ + Message: "request.Banner.Format is required", + } + return nil, err + } + } + + resImps = append(resImps, imp) + + } + return resImps, nil +} + +func getIsUrlSecure(request *openrtb2.BidRequest) (isUrlSecure bool) { + if request.Site != nil { + if request.Site.Page != "" { + pageURL, err := url.Parse(request.Site.Page) + if err == nil { + if request.Site.Domain == "" { + request.Site.Domain = pageURL.Host + } + isUrlSecure = pageURL.Scheme == "https" + } + } + } + return +} + +func validateDevice(request *openrtb2.BidRequest) (err error) { + + if request.Device != nil && request.Device.IP == "" && request.Device.IPv6 == "" { + err = &errortypes.BadInput{ + Message: "request.Device.IP is required", + } + return err + } + return err +} + +func parseImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpRichaudience, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("not found parameters ext in ImpID : %s", imp.ID), + } + return nil, err + } + + var richaudienceExt openrtb_ext.ExtImpRichaudience + if err := json.Unmarshal(bidderExt.Bidder, &richaudienceExt); err != nil { + err = &errortypes.BadInput{ + Message: fmt.Sprintf("invalid parameters ext in ImpID: %s", imp.ID), + } + return nil, err + } + + return &richaudienceExt, nil +} diff --git a/adapters/richaudience/richaudience_test.go b/adapters/richaudience/richaudience_test.go new file mode 100644 index 00000000000..b1655a4d1d6 --- /dev/null +++ b/adapters/richaudience/richaudience_test.go @@ -0,0 +1,161 @@ +package richaudience + +import ( + "net/http" + "testing" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "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" +) + +type richaudienceRequest struct { + ID string `json:"id,omitempty"` + Imp []openrtb2.Imp `json:"imp,omitempty"` + User richaudienceUser `json:"user,omitempty"` + Device richaudienceDevice `json:"device,omitempty"` + Site richaudienceSite `json:"site,omitempty"` + Test int8 `json:"test,omitempty"` +} + +type richaudienceUser struct { + BuyerUID string `json:"buyeruid,omitempty"` + Ext richaudienceUserExt `json:"ext,omitempty"` +} + +type richaudienceUserExt struct { + Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` + Consent string `json:"consent,omitempty"` +} + +type richaudienceDevice struct { + IP string `json:"ip,omitempty"` + IPv6 string `json:"ipv6,omitempty"` + Lmt int8 `json:"lmt,omitempty"` + DNT int8 `json:"dnt,omitempty"` + UA string `json:"ua,omitempty"` +} + +type richaudienceSite struct { + Domain string `json:"domain,omitempty"` + Page string `json:"page,omitempty"` +} + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderRichaudience, config.Adapter{ + Endpoint: "http://ortb.richaudience.com/ortb/?bidder=pbs", + }) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "richaudiencetest", bidder) +} + +func TestGetBuilder(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderRichaudience, config.Adapter{ + Endpoint: "http://ortb.richaudience.com/ortb/?bidder=pbs"}) + + if buildErr != nil { + t.Errorf("error %s", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "richaudience", bidder) +} + +func TestGetSite(t *testing.T) { + raBidRequest := &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Domain: "www.test.com", + }, + } + + richaudienceRequestTest := &richaudienceRequest{ + Site: richaudienceSite{ + Domain: "www.test.com", + }, + } + + getIsUrlSecure(raBidRequest) + + if raBidRequest.Site.Domain != richaudienceRequestTest.Site.Domain { + t.Errorf("error %s", richaudienceRequestTest.Site.Domain) + } + + raBidRequest = &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "http://www.test.com/test", + Domain: "", + }, + } + + richaudienceRequestTest = &richaudienceRequest{ + Site: richaudienceSite{ + Domain: "", + }, + } + + getIsUrlSecure(raBidRequest) +} + +func TestGetDevice(t *testing.T) { + + raBidRequest := &openrtb2.BidRequest{ + Device: &openrtb2.Device{ + IP: "11.222.33.44", + UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36", + }, + } + + richaudienceRequestTest := &richaudienceRequest{ + Device: richaudienceDevice{ + IP: "11.222.33.44", + Lmt: 0, + DNT: 0, + UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36", + }, + } + + validateDevice(raBidRequest) + + if raBidRequest.Device.IP != richaudienceRequestTest.Device.IP { + t.Errorf("error %s", richaudienceRequestTest.Device.IP) + } + + if richaudienceRequestTest.Device.Lmt == 1 { + t.Errorf("error %v", richaudienceRequestTest.Device.Lmt) + } + + if richaudienceRequestTest.Device.DNT == 1 { + t.Errorf("error %v", richaudienceRequestTest.Device.DNT) + } + + if raBidRequest.Device.UA != richaudienceRequestTest.Device.UA { + t.Errorf("error %s", richaudienceRequestTest.Device.UA) + } +} + +func TestResponseEmpty(t *testing.T) { + httpResp := &adapters.ResponseData{ + StatusCode: http.StatusNoContent, + } + bidder := new(adapter) + bidResponse, errs := bidder.MakeBids(nil, nil, httpResp) + + assert.Nil(t, bidResponse, "Expected Nil") + assert.Empty(t, errs, "Errors: %d", len(errs)) +} + +func TestEmptyConfig(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderRichaudience, config.Adapter{ + Endpoint: ``, + ExtraAdapterInfo: ``, + }) + + assert.NoError(t, buildErr) + assert.Empty(t, bidder) +} diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-app.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-app.json new file mode 100644 index 00000000000..430e1a27f02 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-app.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "device": { + "ip": "11.222.33.44", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 0, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "device": { + "ip": "11.222.33.44", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-defaultCurrency.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-defaultCurrency.json new file mode 100644 index 00000000000..a6ebe54e95b --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-defaultCurrency.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-deviceConfig.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-deviceConfig.json new file mode 100644 index 00000000000..8b21864c63f --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-deviceConfig.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "dnt": 1, + "lmt": 1, + "ip": "11.222.33.44" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "dnt": 1, + "lmt": 1, + "ip": "11.222.33.44" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-extUser.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-extUser.json new file mode 100644 index 00000000000..72f5387dcf4 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-extUser.json @@ -0,0 +1,162 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + "eids": [ + { + "source": "id5-sync.com", + "uids": [ + { + "id": "ID5-ZHMOC5mEw0TZiThiUevdyq0gjh7Egh3A4p4i9XGP-w!ID5*NAbWTGXXH8AqlxI7DB9w3qTju41wihkerqwFIZs_FPgAABOqmcXN5sfliX2kQ4Ku", + "atype": 1, + "ext": { + "linkType": 2, + "abTestingControlGroup": false + } + } + ] + } + ], + "consent": "CPItQrlPItQrlAKAfAENBhCsAP_AAHLAAAiQIBtf_X__bX9j" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + "consent": "CPItQrlPItQrlAKAfAENBhCsAP_AAHLAAAiQIBtf_X__bX9j", + "eids": [ + { + "source": "id5-sync.com", + "uids": [ + { + "id": "ID5-ZHMOC5mEw0TZiThiUevdyq0gjh7Egh3A4p4i9XGP-w!ID5*NAbWTGXXH8AqlxI7DB9w3qTju41wihkerqwFIZs_FPgAABOqmcXN5sfliX2kQ4Ku", + "atype": 1, + "ext": { + "linkType": 2, + "abTestingControlGroup": false + } + } + ] + } + ] + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-floorPrice.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-floorPrice.json new file mode 100644 index 00000000000..3dcc0f94db9 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-floorPrice.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true, + "BidFloor": 1.5 + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true, + "BidFloor": 1.5 + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-iPv6.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-iPv6.json new file mode 100644 index 00000000000..f2162ed598b --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-iPv6.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-nosecure.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-nosecure.json new file mode 100644 index 00000000000..ffd92960718 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-nosecure.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "http://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 0, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "http://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-setCurrency.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-setCurrency.json new file mode 100644 index 00000000000..1d970605444 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-setCurrency.json @@ -0,0 +1,131 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true, + "bidfloorcur": "EUR" + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "EUR", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true, + "bidfloorcur": "EUR" + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "EUR", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner-sitePage.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner-sitePage.json new file mode 100644 index 00000000000..a1634235060 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner-sitePage.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/exemplary/single-banner.json b/adapters/richaudience/richaudiencetest/exemplary/single-banner.json new file mode 100644 index 00000000000..a1634235060 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/exemplary/single-banner.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"], + "X-Openrtb-Version": ["2.5"] + }, + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 1, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ff935bea-4661-40bf-95b7-80c354cf0cdc", + "seatbid": [{ + "bid": [{ + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "crid": "999999", + "adm": "", + "adomain": ["richaudience.com"], + "h": 250, + "w": 300 + }] + }] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "47286888", + "impid": "div-gpt-ad-1460505748561-0", + "price": 99, + "adm": "", + "adomain": ["richaudience.com"], + "crid": "999999", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/formatNotFound.json b/adapters/richaudience/richaudiencetest/supplemental/formatNotFound.json new file mode 100644 index 00000000000..01fbd4184c5 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/formatNotFound.json @@ -0,0 +1,39 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "", + "ext": {} + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "request.Banner.Format is required", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/invalidParams.json b/adapters/richaudience/richaudiencetest/supplemental/invalidParams.json new file mode 100644 index 00000000000..2c727621101 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/invalidParams.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "invalid parameters ext in ImpID: div-gpt-ad-1460505748561-0", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/noIPDevice.json b/adapters/richaudience/richaudiencetest/supplemental/noIPDevice.json new file mode 100644 index 00000000000..72148a7b376 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/noIPDevice.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": true + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "", + "ext": {} + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "request.Device.IP is required", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/notFoundParams.json b/adapters/richaudience/richaudiencetest/supplemental/notFoundParams.json new file mode 100644 index 00000000000..7b1cac83c54 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/notFoundParams.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "test": 0, + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD" + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "expectedMakeRequestsErrors": [{ + "value": "not found parameters ext in ImpID : div-gpt-ad-1460505748561-0", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/responseBlank.json b/adapters/richaudience/richaudiencetest/supplemental/responseBlank.json new file mode 100644 index 00000000000..d8f53b9f027 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/responseBlank.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 204, + "body": { + + } + } + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/statusCode400.json b/adapters/richaudience/richaudiencetest/supplemental/statusCode400.json new file mode 100644 index 00000000000..8d67723c856 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/statusCode400.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/statusCodeError.json b/adapters/richaudience/richaudiencetest/supplemental/statusCodeError.json new file mode 100644 index 00000000000..5fd05c12e6a --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/statusCodeError.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "secure": 1, + "tagid" : "OsNsyeF68q", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 502 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 502. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/adapters/richaudience/richaudiencetest/supplemental/unexpectedStatusCode.json b/adapters/richaudience/richaudiencetest/supplemental/unexpectedStatusCode.json new file mode 100644 index 00000000000..ba66f0b6c28 --- /dev/null +++ b/adapters/richaudience/richaudiencetest/supplemental/unexpectedStatusCode.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": {} + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ortb.richaudience.com/ortb/?bidder=pbs", + "body": { + "id": "4d3f84eb-787b-42fb-a0cf-062690dadce3", + "imp": [ + { + "id": "div-gpt-ad-1460505748561-0", + "tagid" : "OsNsyeF68q", + "secure": 1, + "banner": { + "format" : [{ + "w": 300, + "h": 250 + }] + }, + "bidfloor": 1e-05, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "pid": "OsNsyeF68q", + "supplyType": "site", + "test": false + } + } + } + ], + "site": { + "domain": "bridge.richmediastudio.com", + "page": "https://bridge.richmediastudio.com//ab083674fb8200b877a6983126e4477d/PrebidServer/indexRa.html?pbjs_debug=true" + }, + "device": { + "ip": "11.222.33.44", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36" + }, + "user": { + "buyeruid": "189f4055-78a3-46eb-b7fd-0915a1a43bd2a", + "ext": { + + } + } + } + }, + "mockResponse": { + "status": 800 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 800. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] + } \ No newline at end of file diff --git a/config/config.go b/config/config.go index 128b005af76..cdbaeab7fb0 100644 --- a/config/config.go +++ b/config/config.go @@ -861,6 +861,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.revcontent.disabled", true) v.SetDefault("adapters.revcontent.endpoint", "https://trends.revcontent.com/rtb") v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp") + v.SetDefault("adapters.richaudience.endpoint", "http://ortb.richaudience.com/ortb/?bidder=pbs") v.SetDefault("adapters.rtbhouse.endpoint", "http://prebidserver-s2s-ams.creativecdn.com/bidder/prebidserver/bids") v.SetDefault("adapters.rubicon.disabled", true) v.SetDefault("adapters.rubicon.endpoint", "http://exapi-us-east.rubiconproject.com/a/api/exchange.json") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index f34e74ea41b..10a45b99fae 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -2,7 +2,7 @@ package exchange import ( "github.com/prebid/prebid-server/adapters" - ttx "github.com/prebid/prebid-server/adapters/33across" + "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/aceex" "github.com/prebid/prebid-server/adapters/acuityads" "github.com/prebid/prebid-server/adapters/adagio" @@ -97,6 +97,7 @@ import ( "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/revcontent" "github.com/prebid/prebid-server/adapters/rhythmone" + "github.com/prebid/prebid-server/adapters/richaudience" "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" salunamedia "github.com/prebid/prebid-server/adapters/sa_lunamedia" @@ -233,6 +234,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderPulsepoint: pulsepoint.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRhythmone: rhythmone.Builder, + openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, openrtb_ext.BidderRubicon: rubicon.Builder, openrtb_ext.BidderSharethrough: sharethrough.Builder, diff --git a/exchange/bidder.go b/exchange/bidder.go index 0bdaa648619..2aae63e21b8 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -130,6 +130,7 @@ type bidderAdapterConfig struct { } 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 { diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 14a261e314e..427ff6f45b5 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -168,6 +168,7 @@ const ( BidderPulsepoint BidderName = "pulsepoint" BidderRevcontent BidderName = "revcontent" BidderRhythmone BidderName = "rhythmone" + BidderRichaudience BidderName = "richaudience" BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" BidderSharethrough BidderName = "sharethrough" @@ -304,6 +305,7 @@ func CoreBidderNames() []BidderName { BidderPulsepoint, BidderRevcontent, BidderRhythmone, + BidderRichaudience, BidderRTBHouse, BidderRubicon, BidderSharethrough, diff --git a/openrtb_ext/imp_richaudience.go b/openrtb_ext/imp_richaudience.go new file mode 100644 index 00000000000..bdcf7a96db9 --- /dev/null +++ b/openrtb_ext/imp_richaudience.go @@ -0,0 +1,9 @@ +package openrtb_ext + +type ExtImpRichaudience struct { + Pid string `json:"pid"` + SupplyType string `json:"supplyType"` + BidFloor float64 `json:"bidfloor"` + BidFloorCur string `json:"bidfloorcur"` + Test bool `json:"test"` +} diff --git a/static/bidder-info/richaudience.yaml b/static/bidder-info/richaudience.yaml new file mode 100644 index 00000000000..6b51aaa86e3 --- /dev/null +++ b/static/bidder-info/richaudience.yaml @@ -0,0 +1,15 @@ +maintainer: + email: partnerintegrations@richaudience.com +gvlVendorID: 108 +modifyingVastXmlAllowed: true +capabilities: + site: + mediaTypes: + - banner + app: + mediaTypes: + - banner +userSync: + iframe: + url: "https://sync.richaudience.com/74889303289e27f327ad0c6de7be7264/?consentString={{.GDPRConsent}}&r={{.RedirectURL}}" + userMacro: "[PDID]" diff --git a/static/bidder-params/richaudience.json b/static/bidder-params/richaudience.json new file mode 100644 index 00000000000..e10dc59bb30 --- /dev/null +++ b/static/bidder-params/richaudience.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "richaudience Adapter Params", + "description": "A schema which validates params accepted by the richaudience adapter", + "type": "object", + + "properties": { + "pid": { + "type": "string", + "description": "Placement ID" + }, + "supplyType": { + "type": "string", + "description": "Type integration" + }, + "test": { + "type": "boolean", + "description": "TestMode" + }, + "bidFloor": { + "type": "number", + "description": "floor price" + }, + "bidFloorCur": { + "type": "string", + "description": "currency floor price" + } + }, + + "required": ["pid", "supplyType"] +} From f3177408f03f1bbec1db66271918e27425758d49 Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Mon, 25 Oct 2021 13:59:18 -0400 Subject: [PATCH 123/140] Update prometheus/client_golang To Latest (#2052) --- go.mod | 11 +++----- go.sum | 82 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index e673d2218c7..1dad3f3e9de 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/NYTimes/gziphandler v1.1.1 github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf - github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/buger/jsonparser v1.1.1 github.com/cespare/xxhash v1.0.0 // indirect github.com/chasex/glog v0.0.0-20160217080310-c62392af379c @@ -17,23 +16,19 @@ require ( github.com/gofrs/uuid v3.2.0+incompatible github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/influxdata/influxdb v1.6.1 - github.com/julienschmidt/httprouter v1.1.0 + github.com/julienschmidt/httprouter v1.3.0 github.com/lib/pq v1.0.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/mxmCherry/openrtb/v15 v15.0.0 github.com/prebid/go-gdpr v1.10.0 - github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed - github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 - github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect - github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 // indirect + github.com/prometheus/client_golang v1.11.0 + github.com/prometheus/client_model v0.2.0 github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 github.com/rs/cors v1.5.0 github.com/sergi/go-diff v1.0.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/viper v1.8.1 - github.com/stretchr/objx v0.1.1 // indirect 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 diff --git a/go.sum b/go.sum index bcab99e4ebf..fc3dbb34025 100644 --- a/go.sum +++ b/go.sum @@ -45,14 +45,21 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -60,6 +67,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c/go.mod h1:omJZNg0Qu76bxJd+ExohVo8uXzNcGOk2bv7vel460xk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -95,10 +104,18 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -193,16 +210,23 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/influxdata/influxdb v1.6.1 h1:OseoBlzI5ftNI/bczyxSWq6PKRCNEeiXvyWP/wS5fB0= github.com/influxdata/influxdb v1.6.1/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4EI5MABq68= -github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -235,8 +259,11 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 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= @@ -253,21 +280,34 @@ github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prebid/go-gdpr v1.10.0 h1:f9Ua+PAIK97j82QkJtIsohlbyU8961mFphw23hTsoMo= github.com/prebid/go-gdpr v1.10.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY= -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-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -278,6 +318,9 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -338,6 +381,7 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -384,6 +428,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -392,6 +437,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -445,12 +491,15 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -462,6 +511,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -474,6 +524,8 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -482,14 +534,16 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -663,18 +717,22 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/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= From 8eaa2e67006150832a425e60f434aed5c3d267c3 Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Thu, 28 Oct 2021 19:21:41 +0300 Subject: [PATCH 124/140] Adform adapter: convert it to adf adapter alias (#2055) --- adapters/adform/README.md | 1 - adapters/adform/adform.go | 460 -------------- adapters/adform/adform_test.go | 561 ------------------ .../exemplary/multiformat-impression.json | 99 ---- .../exemplary/single-banner-impression.json | 64 -- .../exemplary/single-video-impression.json | 60 -- .../adformtest/supplemental/regs-ext-nil.json | 53 -- .../adformtest/supplemental/user-nil.json | 50 -- adapters/adform/params_test.go | 77 --- config/config.go | 2 +- exchange/adapter_builders.go | 3 +- openrtb_ext/imp_adform.go | 11 - static/bidder-info/adform.yaml | 2 + static/bidder-params/adform.json | 44 +- 14 files changed, 19 insertions(+), 1468 deletions(-) delete mode 100644 adapters/adform/README.md delete mode 100644 adapters/adform/adform.go delete mode 100644 adapters/adform/adform_test.go delete mode 100644 adapters/adform/adformtest/exemplary/multiformat-impression.json delete mode 100644 adapters/adform/adformtest/exemplary/single-banner-impression.json delete mode 100644 adapters/adform/adformtest/exemplary/single-video-impression.json delete mode 100644 adapters/adform/adformtest/supplemental/regs-ext-nil.json delete mode 100644 adapters/adform/adformtest/supplemental/user-nil.json delete mode 100644 adapters/adform/params_test.go delete mode 100644 openrtb_ext/imp_adform.go diff --git a/adapters/adform/README.md b/adapters/adform/README.md deleted file mode 100644 index b4c335fcdf6..00000000000 --- a/adapters/adform/README.md +++ /dev/null @@ -1 +0,0 @@ -Please contact if you would like to build and deploy Prebid server and use it with Adform. diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go deleted file mode 100644 index a432bb075b3..00000000000 --- a/adapters/adform/adform.go +++ /dev/null @@ -1,460 +0,0 @@ -package adform - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" - - "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" - - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/openrtb2" -) - -const version = "0.1.3" - -type AdformAdapter struct { - URL *url.URL - version string -} - -type adformRequest struct { - tid string - userAgent string - ip string - advertisingId string - bidderCode string - isSecure bool - referer string - userId string - adUnits []*adformAdUnit - gdprApplies string - consent string - currency string - eids string - url string -} - -type adformAdUnit struct { - MasterTagId json.Number `json:"mid"` - PriceType string `json:"priceType,omitempty"` - KeyValues string `json:"mkv,omitempty"` - KeyWords string `json:"mkw,omitempty"` - CDims string `json:"cdims,omitempty"` - MinPrice float64 `json:"minp,omitempty"` - Url string `json:"url,omitempty"` - - bidId string - adUnitCode string -} - -type adformBid struct { - ResponseType string `json:"response,omitempty"` - Banner string `json:"banner,omitempty"` - Price float64 `json:"win_bid,omitempty"` - Currency string `json:"win_cur,omitempty"` - Width uint64 `json:"width,omitempty"` - Height uint64 `json:"height,omitempty"` - DealId string `json:"deal_id,omitempty"` - CreativeId string `json:"win_crid,omitempty"` - VastContent string `json:"vast_content,omitempty"` -} - -const priceTypeGross = "gross" -const priceTypeNet = "net" -const defaultCurrency = "USD" - -func isPriceTypeValid(priceType string) (string, bool) { - pt := strings.ToLower(priceType) - valid := pt == priceTypeNet || pt == priceTypeGross - - return pt, valid -} - -// ADAPTER Interface - -// Builder builds a new instance of the Adform adapter for the given bidder with the given config. -func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - uri, err := url.Parse(config.Endpoint) - if err != nil { - return nil, errors.New("unable to parse endpoint") - } - - bidder := &AdformAdapter{ - URL: uri, - version: version, - } - return bidder, nil -} - -func (r *adformRequest) buildAdformUrl(a *AdformAdapter) string { - parameters := url.Values{} - - if r.advertisingId != "" { - parameters.Add("adid", r.advertisingId) - } - parameters.Add("CC", "1") - parameters.Add("rp", "4") - parameters.Add("fd", "1") - parameters.Add("stid", r.tid) - parameters.Add("ip", r.ip) - - priceType := getValidPriceTypeParameter(r.adUnits) - if priceType != "" { - parameters.Add("pt", priceType) - } - - parameters.Add("gdpr", r.gdprApplies) - parameters.Add("gdpr_consent", r.consent) - if r.eids != "" { - parameters.Add("eids", r.eids) - } - - if r.url != "" { - parameters.Add("url", r.url) - } - - URL := *a.URL - URL.RawQuery = parameters.Encode() - - uri := URL.String() - if r.isSecure { - uri = strings.Replace(uri, "http://", "https://", 1) - } - - adUnitsParams := make([]string, 0, len(r.adUnits)) - for _, adUnit := range r.adUnits { - var buffer bytes.Buffer - buffer.WriteString(fmt.Sprintf("mid=%s&rcur=%s", adUnit.MasterTagId, r.currency)) - if adUnit.KeyValues != "" { - buffer.WriteString(fmt.Sprintf("&mkv=%s", adUnit.KeyValues)) - } - if adUnit.KeyWords != "" { - buffer.WriteString(fmt.Sprintf("&mkw=%s", adUnit.KeyWords)) - } - if adUnit.CDims != "" { - buffer.WriteString(fmt.Sprintf("&cdims=%s", adUnit.CDims)) - } - if adUnit.MinPrice > 0 { - buffer.WriteString(fmt.Sprintf("&minp=%.2f", adUnit.MinPrice)) - } - adUnitsParams = append(adUnitsParams, base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(buffer.Bytes())) - } - - return fmt.Sprintf("%s&%s", uri, strings.Join(adUnitsParams, "&")) -} - -func getValidPriceTypeParameter(adUnits []*adformAdUnit) string { - priceTypeParameter := "" - priceType := priceTypeNet - valid := false - for _, adUnit := range adUnits { - pt, v := isPriceTypeValid(adUnit.PriceType) - if v { - valid = v - if pt == priceTypeGross { - priceType = pt - break - } - } - } - - if valid { - priceTypeParameter = priceType - } - return priceTypeParameter -} - -func (r *adformRequest) buildAdformHeaders(a *AdformAdapter) http.Header { - header := http.Header{} - - header.Set("Content-Type", "application/json;charset=utf-8") - header.Set("Accept", "application/json") - header.Set("X-Request-Agent", fmt.Sprintf("PrebidAdapter %s", a.version)) - header.Set("User-Agent", r.userAgent) - header.Set("X-Forwarded-For", r.ip) - if r.referer != "" { - header.Set("Referer", r.referer) - } - - if r.userId != "" { - header.Set("Cookie", fmt.Sprintf("uid=%s;", r.userId)) - } - - return header -} - -func parseAdformBids(response []byte) ([]*adformBid, error) { - var bids []*adformBid - if err := json.Unmarshal(response, &bids); err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } - } - - return bids, nil -} - -// BIDDER Interface - -func (a *AdformAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - adformRequest, errors := openRtbToAdformRequest(request) - if len(adformRequest.adUnits) == 0 { - return nil, errors - } - - requestData := adapters.RequestData{ - Method: "GET", - Uri: adformRequest.buildAdformUrl(a), - Body: nil, - Headers: adformRequest.buildAdformHeaders(a), - } - - requests := []*adapters.RequestData{&requestData} - - return requests, errors -} - -func openRtbToAdformRequest(request *openrtb2.BidRequest) (*adformRequest, []error) { - adUnits := make([]*adformAdUnit, 0, len(request.Imp)) - errors := make([]error, 0, len(request.Imp)) - secure := false - url := "" - - for _, imp := range request.Imp { - params, _, _, err := jsonparser.Get(imp.Ext, "bidder") - if err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - var adformAdUnit adformAdUnit - if err := json.Unmarshal(params, &adformAdUnit); err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - - mid, err := adformAdUnit.MasterTagId.Int64() - if err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: err.Error(), - }) - continue - } - if mid <= 0 { - errors = append(errors, &errortypes.BadInput{ - Message: fmt.Sprintf("master tag(placement) id is invalid=%s", adformAdUnit.MasterTagId), - }) - continue - } - - if !secure && imp.Secure != nil && *imp.Secure == 1 { - secure = true - } - - if url == "" { - url = adformAdUnit.Url - } - - adformAdUnit.bidId = imp.ID - adformAdUnit.adUnitCode = imp.ID - adUnits = append(adUnits, &adformAdUnit) - } - - referer := "" - if request.Site != nil { - referer = request.Site.Page - } - - tid := "" - if request.Source != nil { - tid = request.Source.TID - } - - gdprApplies := "" - var extRegs openrtb_ext.ExtRegs - if request.Regs != nil && request.Regs.Ext != nil { - if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { - errors = append(errors, &errortypes.BadInput{ - Message: err.Error(), - }) - } - if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) { - gdprApplies = strconv.Itoa(int(*extRegs.GDPR)) - } - } - - eids := "" - consent := "" - if request.User != nil && request.User.Ext != nil { - var extUser openrtb_ext.ExtUser - if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { - consent = extUser.Consent - eids = encodeEids(extUser.Eids) - } - } - - requestCurrency := defaultCurrency - if len(request.Cur) != 0 { - hasDefaultCurrency := false - for _, c := range request.Cur { - if defaultCurrency == c { - hasDefaultCurrency = true - break - } - } - if !hasDefaultCurrency { - requestCurrency = request.Cur[0] - } - } - - return &adformRequest{ - adUnits: adUnits, - ip: getIPSafely(request.Device), - advertisingId: getIFASafely(request.Device), - userAgent: getUASafely(request.Device), - isSecure: secure, - referer: referer, - userId: getBuyerUIDSafely(request.User), - tid: tid, - gdprApplies: gdprApplies, - consent: consent, - currency: requestCurrency, - eids: eids, - url: url, - }, errors -} - -func encodeEids(eids []openrtb_ext.ExtUserEid) string { - if eids == nil { - return "" - } - - eidsMap := make(map[string]map[string][]int) - for _, eid := range eids { - _, ok := eidsMap[eid.Source] - if !ok { - eidsMap[eid.Source] = make(map[string][]int) - } - for _, uid := range eid.Uids { - eidsMap[eid.Source][uid.ID] = append(eidsMap[eid.Source][uid.ID], uid.Atype) - } - } - - encodedEids := "" - if eidsString, err := json.Marshal(eidsMap); err == nil { - encodedEids = base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(eidsString) - } - - return encodedEids -} - -func getIPSafely(device *openrtb2.Device) string { - if device == nil { - return "" - } - return device.IP -} - -func getIFASafely(device *openrtb2.Device) string { - if device == nil { - return "" - } - return device.IFA -} - -func getUASafely(device *openrtb2.Device) string { - if device == nil { - return "" - } - return device.UA -} - -func getBuyerUIDSafely(user *openrtb2.User) string { - if user == nil { - return "" - } - return user.BuyerUID -} - -func (a *AdformAdapter) 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), - }} - } - - adformOutput, err := parseAdformBids(response.Body) - if err != nil { - return nil, []error{err} - } - - bidResponse := toOpenRtbBidResponse(adformOutput, internalRequest) - - return bidResponse, nil -} - -func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb2.BidRequest) *adapters.BidderResponse { - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(adformBids)) - currency := bidResponse.Currency - - if len(adformBids) > 0 { - bidResponse.Currency = adformBids[0].Currency - } - - for i, bid := range adformBids { - adm, bidType := getAdAndType(bid) - if adm == "" { - continue - } - - openRtbBid := openrtb2.Bid{ - ID: r.Imp[i].ID, - ImpID: r.Imp[i].ID, - Price: bid.Price, - AdM: adm, - W: int64(bid.Width), - H: int64(bid.Height), - DealID: bid.DealId, - CrID: bid.CreativeId, - } - - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &openRtbBid, BidType: bidType}) - currency = bid.Currency - } - - bidResponse.Currency = currency - - return bidResponse -} - -func getAdAndType(bid *adformBid) (string, openrtb_ext.BidType) { - if bid.ResponseType == "banner" { - return bid.Banner, openrtb_ext.BidTypeBanner - } else if bid.ResponseType == "vast_content" { - return bid.VastContent, openrtb_ext.BidTypeVideo - } - return "", "" -} diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go deleted file mode 100644 index 53a658f9715..00000000000 --- a/adapters/adform/adform_test.go +++ /dev/null @@ -1,561 +0,0 @@ -package adform - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "strconv" - "testing" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/adapterstest" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/stretchr/testify/assert" -) - -func TestJsonSamples(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ - Endpoint: "https://adx.adform.net/adx"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - adapterstest.RunJSONBidderTest(t, "adformtest", bidder) -} - -func TestEndpointMalformed(t *testing.T) { - _, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ - Endpoint: ` https://malformed`}) - - assert.Error(t, buildErr) -} - -type aTagInfo struct { - mid uint32 - priceType string - keyValues string - keyWords string - code string - cdims string - url string - minp float64 - - price float64 - content string - dealId string - creativeId string -} - -type aBidInfo struct { - deviceIP string - deviceUA string - deviceIFA string - tags []aTagInfo - referrer string - width uint64 - height uint64 - tid string - buyerUID string - secure bool - currency string -} - -func createAdformServerResponse(testData aBidInfo) ([]byte, error) { - bids := []adformBid{ - { - ResponseType: "banner", - Banner: testData.tags[0].content, - Price: testData.tags[0].price, - Currency: "EUR", - Width: testData.width, - Height: testData.height, - DealId: testData.tags[0].dealId, - CreativeId: testData.tags[0].creativeId, - }, - {}, - { - ResponseType: "banner", - Banner: testData.tags[2].content, - Price: testData.tags[2].price, - Currency: "EUR", - Width: testData.width, - Height: testData.height, - DealId: testData.tags[2].dealId, - CreativeId: testData.tags[2].creativeId, - }, - { - ResponseType: "vast_content", - VastContent: testData.tags[3].content, - Price: testData.tags[3].price, - Currency: "EUR", - Width: testData.width, - Height: testData.height, - DealId: testData.tags[3].dealId, - CreativeId: testData.tags[3].creativeId, - }, - } - adformServerResponse, err := json.Marshal(bids) - return adformServerResponse, err -} - -func TestOpenRTBRequest(t *testing.T) { - bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{ - Endpoint: "https://adx.adform.net"}) - - if buildErr != nil { - t.Fatalf("Builder returned unexpected error %v", buildErr) - } - - testData := createTestData(true) - request := createOpenRtbRequest(&testData) - - httpRequests, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - - if len(errs) > 0 { - t.Errorf("Got unexpected errors while building HTTP requests: %v", errs) - } - if len(httpRequests) != 1 { - t.Fatalf("Unexpected number of HTTP requests. Got %d. Expected %d", len(httpRequests), 1) - } - - r, err := http.NewRequest(httpRequests[0].Method, httpRequests[0].Uri, bytes.NewReader(httpRequests[0].Body)) - if err != nil { - t.Fatalf("Unexpected request. Got %v", err) - } - r.Header = httpRequests[0].Headers - - errorString := assertAdformServerRequest(testData, r) - if errorString != nil { - t.Errorf("Request error: %s", *errorString) - } -} - -func TestOpenRTBIncorrectRequest(t *testing.T) { - bidder := new(AdformAdapter) - request := &openrtb2.BidRequest{ - ID: "test-request-id", - Imp: []openrtb2.Imp{ - {ID: "incorrect-bidder-field", Ext: json.RawMessage(`{"bidder1": { "mid": "32344" }}`)}, - {ID: "incorrect-adform-params", Ext: json.RawMessage(`{"bidder": { : "33" }}`)}, - {ID: "mid-integer", Ext: json.RawMessage(`{"bidder": { "mid": 1.234 }}`)}, - {ID: "mid-greater-then-zero", Ext: json.RawMessage(`{"bidder": { "mid": -1 }}`)}, - }, - Device: &openrtb2.Device{UA: "ua", IP: "ip"}, - User: &openrtb2.User{BuyerUID: "buyerUID"}, - } - - httpRequests, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - - if len(errs) != len(request.Imp) { - t.Errorf("%d Imp objects should have errors. but was %d errors", len(request.Imp), len(errs)) - } - if len(httpRequests) != 0 { - t.Fatalf("All Imp objects have errors, but requests count: %d. Expected %d", len(httpRequests), 0) - } -} - -func createTestData(secure bool) aBidInfo { - testData := aBidInfo{ - 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", - deviceIFA: "6D92078A-8246-4BA4-AE5B-76104861E7DC", - referrer: "http://test.com", - tid: "transaction-id", - buyerUID: "user-id", - tags: []aTagInfo{ - {mid: 32344, keyValues: "color:red,age:30-40", keyWords: "red,blue", cdims: "300x300,400x200", priceType: "gross", code: "code1", price: 1.23, content: "banner-content1", dealId: "dealId1", creativeId: "creativeId1"}, - {mid: 32345, priceType: "net", code: "code2", minp: 23.1, cdims: "300x200"}, // no bid for ad unit - {mid: 32346, code: "code3", price: 1.24, content: "banner-content2", dealId: "dealId2", url: "https://adform.com?a=b"}, - {mid: 32347, code: "code4", content: "vast-xml"}, - }, - secure: secure, - currency: "EUR", - } - return testData -} - -func createOpenRtbRequest(testData *aBidInfo) *openrtb2.BidRequest { - secure := int8(0) - if testData.secure { - secure = int8(1) - } - - bidRequest := &openrtb2.BidRequest{ - ID: "test-request-id", - Imp: make([]openrtb2.Imp, len(testData.tags)), - Site: &openrtb2.Site{ - Page: testData.referrer, - }, - Device: &openrtb2.Device{ - UA: testData.deviceUA, - IP: testData.deviceIP, - IFA: testData.deviceIFA, - }, - Source: &openrtb2.Source{ - TID: testData.tid, - }, - User: &openrtb2.User{ - BuyerUID: testData.buyerUID, - }, - } - for i, tag := range testData.tags { - bidRequest.Imp[i] = openrtb2.Imp{ - ID: tag.code, - Secure: &secure, - Ext: json.RawMessage(fmt.Sprintf("{\"bidder\": %s}", formatAdUnitJson(tag))), - Banner: &openrtb2.Banner{}, - } - } - - regs := getRegs() - bidRequest.Regs = ®s - bidRequest.User.Ext = getUserExt() - - bidRequest.Cur = make([]string, 1) - bidRequest.Cur[0] = testData.currency - - return bidRequest -} - -func TestOpenRTBStandardResponse(t *testing.T) { - testData := createTestData(true) - request := createOpenRtbRequest(&testData) - expectedTypes := []openrtb_ext.BidType{ - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeBanner, - openrtb_ext.BidTypeVideo, - } - - responseBody, err := createAdformServerResponse(testData) - if err != nil { - t.Fatalf("Unable to create server response: %v", err) - return - } - httpResponse := &adapters.ResponseData{StatusCode: http.StatusOK, Body: responseBody} - - bidder := new(AdformAdapter) - bidResponse, errs := bidder.MakeBids(request, nil, httpResponse) - - if len(bidResponse.Bids) != 3 { - t.Fatalf("Expected 3 bids. Got %d", len(bidResponse.Bids)) - } - if len(errs) != 0 { - t.Errorf("Expected 0 errors. Got %d", len(errs)) - } - - for i, typeBid := range bidResponse.Bids { - - if typeBid.BidType != expectedTypes[i] { - t.Errorf("Expected a %s bid. Got: %s", expectedTypes[i], typeBid.BidType) - } - bid := typeBid.Bid - matched := false - - for _, tag := range testData.tags { - if bid.ID == tag.code { - matched = true - if bid.Price != tag.price { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) - } - if bid.W != int64(testData.width) || bid.H != int64(testData.height) { - t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.W, bid.H, testData.width, testData.height) - } - if bid.AdM != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.AdM, tag.content) - } - if bid.DealID != tag.dealId { - t.Errorf("Incorrect deal id '%s' expected '%s'", bid.DealID, tag.dealId) - } - if bid.CrID != tag.creativeId { - t.Errorf("Incorrect creative id '%s' expected '%s'", bid.CrID, tag.creativeId) - } - } - } - if !matched { - t.Errorf("Received bid with unknown id '%s'", bid.ID) - } - } -} - -func TestOpenRTBSurpriseResponse(t *testing.T) { - bidder := new(AdformAdapter) - - bidResponse, errs := bidder.MakeBids(nil, nil, - &adapters.ResponseData{StatusCode: http.StatusNoContent, Body: []byte("")}) - if bidResponse != nil && errs != nil { - t.Fatalf("Expected no bids and no errors. Got %d bids and %d", len(bidResponse.Bids), len(errs)) - } - - bidResponse, errs = bidder.MakeBids(nil, nil, - &adapters.ResponseData{StatusCode: http.StatusServiceUnavailable, Body: []byte("")}) - if bidResponse != nil || len(errs) != 1 { - t.Fatalf("Expected one error and no bids. Got %d bids and %d", len(bidResponse.Bids), len(errs)) - } - - bidResponse, errs = bidder.MakeBids(nil, nil, - &adapters.ResponseData{StatusCode: http.StatusOK, Body: []byte("{:'not-valid-json'}")}) - if bidResponse != nil || len(errs) != 1 { - t.Fatalf("Expected one error and no bids. Got %d bids and %d", len(bidResponse.Bids), len(errs)) - } -} - -// helpers - -func getRegs() openrtb2.Regs { - var gdpr int8 = 1 - regsExt := openrtb_ext.ExtRegs{ - GDPR: &gdpr, - } - regs := openrtb2.Regs{} - regsExtData, err := json.Marshal(regsExt) - if err == nil { - regs.Ext = regsExtData - } - return regs -} - -func getUserExt() []byte { - eids := []openrtb_ext.ExtUserEid{ - { - Source: "test.com", - Uids: []openrtb_ext.ExtUserEidUid{ - { - ID: "some_user_id", - Atype: 1, - }, - { - ID: "other_user_id", - }, - }, - }, - { - Source: "test2.org", - Uids: []openrtb_ext.ExtUserEidUid{ - { - ID: "other_user_id", - Atype: 2, - }, - }, - }, - } - - userExt := openrtb_ext.ExtUser{ - Eids: eids, - Consent: "abc", - } - userExtData, err := json.Marshal(userExt) - if err == nil { - return userExtData - } - - return nil -} - -func formatAdUnitJson(tag aTagInfo) string { - return fmt.Sprintf("{ \"mid\": %d%s%s%s%s%s%s}", - tag.mid, - formatAdUnitParam("priceType", tag.priceType), - formatAdUnitParam("mkv", tag.keyValues), - formatAdUnitParam("mkw", tag.keyWords), - formatAdUnitParam("cdims", tag.cdims), - formatAdUnitParam("url", tag.url), - formatDemicalAdUnitParam("minp", tag.minp)) -} - -func formatDemicalAdUnitParam(fieldName string, fieldValue float64) string { - if fieldValue > 0 { - return fmt.Sprintf(", \"%s\": %.2f", fieldName, fieldValue) - } - - return "" -} - -func formatAdUnitParam(fieldName string, fieldValue string) string { - if fieldValue != "" { - return fmt.Sprintf(", \"%s\": \"%s\"", fieldName, fieldValue) - } - - return "" -} - -func assertAdformServerRequest(testData aBidInfo, r *http.Request) *string { - if ok, err := equal("GET", r.Method, "HTTP method"); !ok { - return err - } - if testData.secure { - if ok, err := equal("https", r.URL.Scheme, "Scheme"); !ok { - return err - } - } - - midsWithCurrency := "bWlkPTMyMzQ0JnJjdXI9RVVSJm1rdj1jb2xvcjpyZWQsYWdlOjMwLTQwJm1rdz1yZWQsYmx1ZSZjZGltcz0zMDB4MzAwLDQwMHgyMDA&bWlkPTMyMzQ1JnJjdXI9RVVSJmNkaW1zPTMwMHgyMDAmbWlucD0yMy4xMA&bWlkPTMyMzQ2JnJjdXI9RVVS&bWlkPTMyMzQ3JnJjdXI9RVVS" - queryString := "CC=1&adid=6D92078A-8246-4BA4-AE5B-76104861E7DC&eids=eyJ0ZXN0LmNvbSI6eyJvdGhlcl91c2VyX2lkIjpbMF0sInNvbWVfdXNlcl9pZCI6WzFdfSwidGVzdDIub3JnIjp7Im90aGVyX3VzZXJfaWQiOlsyXX19&fd=1&gdpr=1&gdpr_consent=abc&ip=111.111.111.111&pt=gross&rp=4&stid=transaction-id&url=https%3A%2F%2Fadform.com%3Fa%3Db&" + midsWithCurrency - - if ok, err := equal(queryString, r.URL.RawQuery, "Query string"); !ok { - return err - } - if ok, err := equal("application/json;charset=utf-8", r.Header.Get("Content-Type"), "Content type"); !ok { - return err - } - if ok, err := equal(testData.deviceUA, r.Header.Get("User-Agent"), "User agent"); !ok { - return err - } - if ok, err := equal(testData.deviceIP, r.Header.Get("X-Forwarded-For"), "Device IP"); !ok { - return err - } - if ok, err := equal(testData.referrer, r.Header.Get("Referer"), "Referer"); !ok { - return err - } - if ok, err := equal(fmt.Sprintf("uid=%s;", testData.buyerUID), r.Header.Get("Cookie"), "Buyer ID"); !ok { - return err - } - return nil -} - -func equal(expected string, actual string, message string) (bool, *string) { - if expected != actual { - message := fmt.Sprintf("%s '%s' doesn't match '%s'", message, actual, expected) - return false, &message - } - return true, nil -} - -// Price type parameter tests - -func TestPriceTypeValidation(t *testing.T) { - // Arrange - priceTypeTestCases := map[string]bool{ - "net": true, - "NET": true, - "nEt": true, - "nt": false, - "gross": true, - "GROSS": true, - "groSS": true, - "gorss": false, - "": false, - } - - // Act - for priceType, expected := range priceTypeTestCases { - _, valid := isPriceTypeValid(priceType) - - // Assert - if expected != valid { - t.Fatalf("Unexpected result for '%s' price type. Got valid = %s. Expected valid = %s", priceType, strconv.FormatBool(valid), strconv.FormatBool(expected)) - } - } -} - -func TestPriceTypeUrlParameterCreation(t *testing.T) { - // Arrange - priceTypeParameterTestCases := map[string][]*adformAdUnit{ - "": {{MasterTagId: "123"}, {MasterTagId: "456"}}, - "net": {{MasterTagId: "123", PriceType: priceTypeNet}, {MasterTagId: "456"}, {MasterTagId: "789", PriceType: priceTypeNet}}, - "gross": {{MasterTagId: "123", PriceType: priceTypeNet}, {MasterTagId: "456", PriceType: priceTypeGross}, {MasterTagId: "789", PriceType: priceTypeNet}}, - } - - // Act - for expected, adUnits := range priceTypeParameterTestCases { - parameter := getValidPriceTypeParameter(adUnits) - - // Assert - if expected != parameter { - t.Fatalf("Unexpected result for price type parameter. Got '%s'. Expected '%s'", parameter, expected) - } - } -} - -// Asserts that toOpenRtbBidResponse() creates a *adapters.BidderResponse with -// the currency of the last valid []*adformBid element and the expected number of bids -func TestToOpenRtbBidResponse(t *testing.T) { - expectedBids := 4 - lastCurrency, anotherCurrency, emptyCurrency := "EUR", "USD", "" - - request := &openrtb2.BidRequest{ - ID: "test-request-id", - Imp: []openrtb2.Imp{ - { - ID: "banner-imp-no1", - Ext: json.RawMessage(`{"bidder1": { "mid": "32341" }}`), - Banner: &openrtb2.Banner{}, - }, - { - ID: "banner-imp-no2", - Ext: json.RawMessage(`{"bidder1": { "mid": "32342" }}`), - Banner: &openrtb2.Banner{}, - }, - { - ID: "banner-imp-no3", - Ext: json.RawMessage(`{"bidder1": { "mid": "32343" }}`), - Banner: &openrtb2.Banner{}, - }, - { - ID: "banner-imp-no4", - Ext: json.RawMessage(`{"bidder1": { "mid": "32344" }}`), - Banner: &openrtb2.Banner{}, - }, - { - ID: "video-imp-no4", - Ext: json.RawMessage(`{"bidder1": { "mid": "32345" }}`), - Banner: &openrtb2.Banner{}, - }, - }, - Device: &openrtb2.Device{UA: "ua", IP: "ip"}, - User: &openrtb2.User{BuyerUID: "buyerUID"}, - } - - testAdformBids := []*adformBid{ - { - ResponseType: "banner", - Banner: "banner-content1", - Price: 1.23, - Currency: anotherCurrency, - Width: 300, - Height: 200, - DealId: "dealId1", - CreativeId: "creativeId1", - }, - {}, - { - ResponseType: "banner", - Banner: "banner-content3", - Price: 1.24, - Currency: emptyCurrency, - Width: 300, - Height: 200, - DealId: "dealId3", - CreativeId: "creativeId3", - }, - { - ResponseType: "banner", - Banner: "banner-content4", - Price: 1.25, - Currency: emptyCurrency, - Width: 300, - Height: 200, - DealId: "dealId4", - CreativeId: "creativeId4", - }, - { - ResponseType: "vast_content", - VastContent: "vast-content", - Price: 1.25, - Currency: lastCurrency, - Width: 300, - Height: 200, - DealId: "dealId4", - CreativeId: "creativeId4", - }, - } - - actualBidResponse := toOpenRtbBidResponse(testAdformBids, request) - - assert.Equalf(t, expectedBids, len(actualBidResponse.Bids), "bid count") - assert.Equalf(t, lastCurrency, actualBidResponse.Currency, "currency") -} diff --git a/adapters/adform/adformtest/exemplary/multiformat-impression.json b/adapters/adform/adformtest/exemplary/multiformat-impression.json deleted file mode 100644 index 5b3067ab927..00000000000 --- a/adapters/adform/adformtest/exemplary/multiformat-impression.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "banner-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "mid": 12345 - } - } - }, - { - "id": "video-imp-id", - "video": { - "w": 640, - "h": 480 - }, - "ext": { - "bidder": { - "mid": 54321 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE&bWlkPTU0MzIxJnJjdXI9VVNE" - }, - "mockResponse": { - "status": 200, - "body": [ - { - "response": "banner", - "banner": "", - "win_bid": 0.5, - "win_cur": "USD", - "width": 300, - "height": 250, - "deal_id": null, - "win_crid": "20078830" - }, - { - "response": "vast_content", - "vast_content": "", - "win_bid": 0.7, - "win_cur": "USD", - "width": 640, - "height": 480, - "deal_id": "DID-123-22", - "win_crid": "20078831" - } - ] - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "banner-imp-id", - "impid": "banner-imp-id", - "price": 0.5, - "adm": "", - "crid": "20078830", - "w": 300, - "h": 250 - }, - "type": "banner" - }, - { - "bid": { - "id": "video-imp-id", - "impid": "video-imp-id", - "price": 0.7, - "adm": "", - "crid": "20078831", - "dealid": "DID-123-22", - "w": 640, - "h": 480 - }, - "type": "video" - } - ] - } - ] - } diff --git a/adapters/adform/adformtest/exemplary/single-banner-impression.json b/adapters/adform/adformtest/exemplary/single-banner-impression.json deleted file mode 100644 index 8a5f81c8edb..00000000000 --- a/adapters/adform/adformtest/exemplary/single-banner-impression.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - } - ] - }, - "ext": { - "bidder": { - "mid": 12345 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE" - }, - "mockResponse": { - "status": 200, - "body": [ - { - "response": "banner", - "banner": "", - "win_bid": 0.5, - "win_cur": "USD", - "width": 300, - "height": 250, - "deal_id": null, - "win_crid": "20078830" - } - ] - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 0.5, - "adm": "", - "crid": "20078830", - "w": 300, - "h": 250 - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/adform/adformtest/exemplary/single-video-impression.json b/adapters/adform/adformtest/exemplary/single-video-impression.json deleted file mode 100644 index 383e091b3f7..00000000000 --- a/adapters/adform/adformtest/exemplary/single-video-impression.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "w": 640, - "h": 480 - }, - "ext": { - "bidder": { - "mid": 54321 - } - } - } - ] - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTU0MzIxJnJjdXI9VVNE" - }, - "mockResponse": { - "status": 200, - "body": [ - { - "response": "vast_content", - "vast_content": "", - "win_bid": 0.5, - "win_cur": "USD", - "width": 640, - "height": 480, - "deal_id": null, - "win_crid": "20078830" - } - ] - } - } - ], - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 0.5, - "adm": "", - "crid": "20078830", - "w": 640, - "h": 480 - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/adform/adformtest/supplemental/regs-ext-nil.json b/adapters/adform/adformtest/supplemental/regs-ext-nil.json deleted file mode 100644 index 377c48b0445..00000000000 --- a/adapters/adform/adformtest/supplemental/regs-ext-nil.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [{ - "id": "test-imp-id", - "banner": { - "format": [{ - "w": 300, - "h": 250 - }] - }, - "ext": { - "bidder": { - "mid": 12345 - } - } - }], - "regs": {} - }, - "httpCalls": [{ - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE" - }, - "mockResponse": { - "status": 200, - "body": [{ - "response": "banner", - "banner": "", - "win_bid": 0.5, - "win_cur": "USD", - "width": 300, - "height": 250, - "deal_id": null, - "win_crid": "20078830" - }] - } - }], - "expectedBidResponses": [{ - "currency": "USD", - "bids": [{ - "bid": { - "id": "test-imp-id", - "impid": "test-imp-id", - "price": 0.5, - "adm": "", - "crid": "20078830", - "w": 300, - "h": 250 - }, - "type": "banner" - }] - }] -} \ No newline at end of file diff --git a/adapters/adform/adformtest/supplemental/user-nil.json b/adapters/adform/adformtest/supplemental/user-nil.json deleted file mode 100644 index 96ea1dbff71..00000000000 --- a/adapters/adform/adformtest/supplemental/user-nil.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "mockBidRequest": { - "id": "unsupported-audio-request", - "imp": [ - { - "id": "unsupported-audio-imp", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "mid": 1, - "priceType": "gross" - } - } - } - ], - "regs": { - "ext": { - "gdpr": 1 - } - }, - "user": { - "ext": { - "consent": "abc2" - } - } - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=1&gdpr_consent=abc2&ip=&pt=gross&rp=4&stid=&bWlkPTEmcmN1cj1VU0Q" - }, - "mockResponse": { - "status": 204 - } - } - ], - "expectedBidResponses": [] -} diff --git a/adapters/adform/params_test.go b/adapters/adform/params_test.go deleted file mode 100644 index b392463f426..00000000000 --- a/adapters/adform/params_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package adform - -import ( - "encoding/json" - "testing" - - "github.com/prebid/prebid-server/openrtb_ext" -) - -// This file actually intends to test static/bidder-params/adform.json -// -// These also validate the format of the external API: request.imp[i].ext.adform - -// 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.BidderAdform, 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.BidderAdform, json.RawMessage(invalidParam)); err == nil { - t.Errorf("Schema allowed unexpected params: %s", invalidParam) - } - } -} - -var validParams = []string{ - `{"mid":123}`, - `{"mid":"123"}`, - `{"mid":123,"priceType":"gross"}`, - `{"mid":"123","priceType":"net"}`, - `{"mid":"123","mkv":" color :blue , length : 350"}`, - `{"mid":"123","mkv":"color:"}`, - `{"mid":"123","mkw":"green,male"}`, - `{"mid":"123","mkv":" ","mkw":" "}`, - `{"mid":"123","cdims":"500x300,400x200","mkw":" "}`, - `{"mid":"123","cdims":"500x300","mkv":" ","mkw":" "}`, - `{"mid":"123","minp":2.1}`, - `{"mid":"123","url":"https://adform.com/page"}`, -} - -var invalidParams = []string{ - ``, - `null`, - `true`, - `5`, - `4.2`, - `[]`, - `{}`, - `{"notmid":"123"}`, - `{"mid":"123","priceType":"ne"}`, - `{"mid":"123","mkv":"color:blue,:350"}`, - `{"mid":"123","mkv":"color:blue;length:350"}`, - `{"mid":"123","mkv":"color"}`, - `{"mid":"123","mkv":"color:blue,l&ngth:350"}`, - `{"mid":"123","mkv":"color::blue"}`, - `{"mid":"123","mkw":"fem&le"}`, - `{"mid":"123","minp":"2.1"}`, - `{"mid":"123","cdims":"500x300:400:200","mkw":" "}`, - `{"mid":"123","cdims":"500x300,400:200","mkv":" ","mkw":" "}`, - `{"mid":"123","url":10}`, -} diff --git a/config/config.go b/config/config.go index cdbaeab7fb0..fd5f5e5bd15 100644 --- a/config/config.go +++ b/config/config.go @@ -763,7 +763,7 @@ func SetupViper(v *viper.Viper, filename string) { 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.adform.endpoint", "https://adx.adform.net/adx/openrtb") v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1") v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json") v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 10a45b99fae..70e6e18befe 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -7,7 +7,6 @@ import ( "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" "github.com/prebid/prebid-server/adapters/adkernel" @@ -142,7 +141,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAcuityAds: acuityads.Builder, openrtb_ext.BidderAdagio: adagio.Builder, openrtb_ext.BidderAdf: adf.Builder, - openrtb_ext.BidderAdform: adform.Builder, + openrtb_ext.BidderAdform: adf.Builder, openrtb_ext.BidderAdgeneration: adgeneration.Builder, openrtb_ext.BidderAdhese: adhese.Builder, openrtb_ext.BidderAdkernel: adkernel.Builder, diff --git a/openrtb_ext/imp_adform.go b/openrtb_ext/imp_adform.go deleted file mode 100644 index 3206ece7c9b..00000000000 --- a/openrtb_ext/imp_adform.go +++ /dev/null @@ -1,11 +0,0 @@ -package openrtb_ext - -type ExtImpAdform struct { - MasterTagId string `json:"mid"` - PriceType string `json:"priceType,omitempty"` - KeyValues string `json:"mkv,omitempty"` - KeyWords string `json:"mkw,omitempty"` - CDims string `json:"cdims,omitempty"` - MinPrice float64 `json:"minp,omitempty"` - Url string `json:"url,omitempty"` -} diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index c8c1d91565a..2e312bff788 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -5,10 +5,12 @@ capabilities: app: mediaTypes: - banner + - native - video site: mediaTypes: - banner + - native - video userSync: redirect: diff --git a/static/bidder-params/adform.json b/static/bidder-params/adform.json index 6c8a67732d5..906241ffb34 100644 --- a/static/bidder-params/adform.json +++ b/static/bidder-params/adform.json @@ -1,42 +1,28 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "title": "Adform Adapter Params", - "description": "A schema which validates params accepted by the Adform adapter", + "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" }, - "priceType": { - "type": "string", - "enum": ["gross", "net"], - "description": "An expected price type (net or gross) of bids." + "inv": { + "type": ["integer"], + "description": "An ID which identifies the Adform inventory source id" }, - "mkv": { - "type": "string", - "description": "Comma-separated key-value pairs. Forbidden symbols: &. Example: mkv='color:blue,length:350'", - "pattern": "^(\\s*|((\\s*[^,:&\\s]+\\s*:[^,:&]*)(,\\s*[^,:&\\s]+\\s*:[^,:&]*)*))$" - }, - "mkw": { - "type": "string", - "description": "Comma-separated keywords. Forbidden symbols: &.", - "pattern": "^[^&]*$" - }, - "cdims": { - "type": "string", - "description": "Comma-separated creative dimensions.", - "pattern": "(^\\d+x\\d+)(,\\d+x\\d+)*$" - }, - "minp": { - "type": "number", - "description": "The minimum CPM price.", - "minimum": 0 - }, - "url": { - "type": "string", - "description": "Custom URL for targeting." + "mname": { + "type": ["string"], + "description": "A Name which identifies the placement selling the impression" } }, - "required": ["mid"] + "anyOf":[ + { + "required": ["mid"] + }, { + "required": ["inv", "mname"] + } + ] } From 2ff6d22afa43862b830fb3e0f4027b3d390981d2 Mon Sep 17 00:00:00 2001 From: Mikhail Ivanchenko Date: Thu, 28 Oct 2021 20:28:47 +0300 Subject: [PATCH 125/140] NextMillennium Bid Adapter: Fix seatbid count (#2061) --- adapters/nextmillennium/nextmillennium.go | 23 ++++++++++--------- .../{bad-response.json => empty-seatbid.json} | 7 +----- 2 files changed, 13 insertions(+), 17 deletions(-) rename adapters/nextmillennium/nextmillenniumtest/supplemental/{bad-response.json => empty-seatbid.json} (88%) diff --git a/adapters/nextmillennium/nextmillennium.go b/adapters/nextmillennium/nextmillennium.go index 9171b21c714..68ca4dc22f2 100644 --- a/adapters/nextmillennium/nextmillennium.go +++ b/adapters/nextmillennium/nextmillennium.go @@ -119,25 +119,26 @@ func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR return nil, []error{&errortypes.BadServerResponse{Message: msg}} } + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { msg = fmt.Sprintf("Bad server response: %d", err) return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - if len(bidResp.SeatBid) != 1 { - var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) - return nil, []error{&errortypes.BadServerResponse{Message: msg}} + + if len(bidResp.SeatBid) == 0 { + return nil, nil } - seatBid := bidResp.SeatBid[0] - bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) - for i := 0; i < len(seatBid.Bid); i++ { - bid := seatBid.Bid[i] - bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ - Bid: &bid, - BidType: openrtb_ext.BidTypeBanner, - }) + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: openrtb_ext.BidTypeBanner, + }) + } } return bidResponse, nil } diff --git a/adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json similarity index 88% rename from adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json rename to adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json index 64de88abc40..8c344d57214 100644 --- a/adapters/nextmillennium/nextmillenniumtest/supplemental/bad-response.json +++ b/adapters/nextmillennium/nextmillenniumtest/supplemental/empty-seatbid.json @@ -49,10 +49,5 @@ } } ], - "expectedMakeBidsErrors": [ - { - "value": "Invalid SeatBids count: 0", - "comparison": "literal" - } - ] + "expectedBidResponses": [] } From 56e11d0f8831bf35ef2b74146831b1b7fdb237a1 Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Fri, 29 Oct 2021 22:11:53 +0200 Subject: [PATCH 126/140] New Adapter: Adnuntius (#2014) --- adapters/adnuntius/adnuntius.go | 303 ++++++++++++++++++ adapters/adnuntius/adnuntius_test.go | 46 +++ .../exemplary/simple-banner.json | 99 ++++++ .../supplemental/check-gdpr.json | 109 +++++++ .../supplemental/check-userId.json | 101 ++++++ .../supplemental/height-error.json | 84 +++++ .../supplemental/native-error.json | 25 ++ .../supplemental/status-400.json | 54 ++++ .../supplemental/test-networks.json | 97 ++++++ .../supplemental/video-error.json | 26 ++ .../supplemental/width-error.json | 84 +++++ adapters/adnuntius/params_test.go | 60 ++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_adnuntius.go | 6 + static/bidder-info/adnuntius.yaml | 10 + static/bidder-params/adnuntius.json | 19 ++ 18 files changed, 1128 insertions(+) create mode 100644 adapters/adnuntius/adnuntius.go create mode 100644 adapters/adnuntius/adnuntius_test.go create mode 100644 adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-userId.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/height-error.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/native-error.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/status-400.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/test-networks.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/video-error.json create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/width-error.json create mode 100644 adapters/adnuntius/params_test.go create mode 100644 openrtb_ext/imp_adnuntius.go create mode 100644 static/bidder-info/adnuntius.yaml create mode 100644 static/bidder-params/adnuntius.json diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go new file mode 100644 index 00000000000..f556081ae70 --- /dev/null +++ b/adapters/adnuntius/adnuntius.go @@ -0,0 +1,303 @@ +package adnuntius + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "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" + "github.com/prebid/prebid-server/util/timeutil" +) + +type QueryString map[string]string +type adapter struct { + time timeutil.Time + endpoint string +} +type adnAdunit struct { + AuId string `json:"auId"` + TargetId string `json:"targetId"` +} + +type AdnResponse struct { + AdUnits []struct { + AuId string + TargetId string + Html string + ResponseId string + Ads []struct { + Bid struct { + Amount float64 + Currency string + } + AdId string + CreativeWidth string + CreativeHeight string + CreativeId string + LineItemId string + Html string + DestinationUrls map[string]string + } + } +} +type adnMetaData struct { + Usi string `json:"usi,omitempty"` +} +type adnRequest struct { + AdUnits []adnAdunit `json:"adUnits"` + MetaData adnMetaData `json:"metaData,omitempty"` + Context string `json:"context,omitempty"` +} + +const defaultNetwork = "default" +const defaultSite = "unknown" +const minutesInHour = 60 + +// Builder builds a new instance of the Adnuntius adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + time: &timeutil.RealTime{}, + endpoint: config.Endpoint, + } + + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + return a.generateRequests(*request) +} + +func setHeaders() http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return headers +} + +func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter) (string, []error) { + uri, err := url.Parse(a.endpoint) + if err != nil { + return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)} + } + + gdpr, consent, err := getGDPR(&ortbRequest) + if err != nil { + return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)} + } + + _, offset := a.time.Now().Zone() + tzo := -offset / minutesInHour + + q := uri.Query() + if gdpr != "" && consent != "" { + q.Set("gdpr", gdpr) + q.Set("consentString", consent) + } + q.Set("tzo", fmt.Sprint(tzo)) + q.Set("format", "json") + + url := a.endpoint + "?" + q.Encode() + return url, nil +} + +/* + Generate the requests to Adnuntius to reduce the amount of requests going out. +*/ +func (a *adapter) generateRequests(ortbRequest openrtb2.BidRequest) ([]*adapters.RequestData, []error) { + var requestData []*adapters.RequestData + networkAdunitMap := make(map[string][]adnAdunit) + headers := setHeaders() + + endpoint, err := makeEndpointUrl(ortbRequest, a) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("failed to parse URL: %s", err), + }} + } + + for _, imp := range ortbRequest.Imp { + if imp.Banner == nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("ignoring imp id=%s, Adnuntius supports only Banner", imp.ID), + }} + } + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling ExtImpBidder: %s", err.Error()), + }} + } + + var adnuntiusExt openrtb_ext.ImpExtAdnunitus + if err := json.Unmarshal(bidderExt.Bidder, &adnuntiusExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling ExtImpBmtm: %s", err.Error()), + }} + } + + network := defaultNetwork + if adnuntiusExt.Network != "" { + network = adnuntiusExt.Network + } + + networkAdunitMap[network] = append( + networkAdunitMap[network], + adnAdunit{ + AuId: adnuntiusExt.Auid, + TargetId: fmt.Sprintf("%s-%s", adnuntiusExt.Auid, imp.ID), + }) + } + + site := defaultSite + if ortbRequest.Site != nil && ortbRequest.Site.Page != "" { + site = ortbRequest.Site.Page + } + + for _, networkAdunits := range networkAdunitMap { + + adnuntiusRequest := adnRequest{ + AdUnits: networkAdunits, + Context: site, + } + + ortbUser := ortbRequest.User + if ortbUser != nil { + ortbUserId := ortbRequest.User.ID + if ortbUserId != "" { + adnuntiusRequest.MetaData.Usi = ortbRequest.User.ID + } + } + + adnJson, err := json.Marshal(adnuntiusRequest) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error unmarshalling adnuntius request: %s", err.Error()), + }} + } + + requestData = append(requestData, &adapters.RequestData{ + Method: http.MethodPost, + Uri: endpoint, + Body: adnJson, + Headers: headers, + }) + + } + + return requestData, nil +} + +func (a *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Status code: %d, Request malformed", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Status code: %d, Something went wrong with your request", response.StatusCode), + }} + } + + var adnResponse AdnResponse + if err := json.Unmarshal(response.Body, &adnResponse); err != nil { + return nil, []error{err} + } + + bidResponse, bidErr := generateBidResponse(&adnResponse, request) + if bidErr != nil { + return nil, bidErr + } + + return bidResponse, nil +} + +func getGDPR(request *openrtb2.BidRequest) (string, string, error) { + gdpr := "" + var extRegs openrtb_ext.ExtRegs + if request.Regs != nil { + if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { + return "", "", fmt.Errorf("failed to parse ExtRegs in Adnuntius GDPR check: %v", err) + } + if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) { + gdpr = strconv.Itoa(int(*extRegs.GDPR)) + } + } + + consent := "" + if request.User != nil && request.User.Ext != nil { + var extUser openrtb_ext.ExtUser + if err := json.Unmarshal(request.User.Ext, &extUser); err != nil { + return "", "", fmt.Errorf("failed to parse ExtUser in Adnuntius GDPR check: %v", err) + } + consent = extUser.Consent + } + + return gdpr, consent, nil +} + +func generateBidResponse(adnResponse *AdnResponse, request *openrtb2.BidRequest) (*adapters.BidderResponse, []error) { + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(adnResponse.AdUnits)) + var currency string + + for i, adunit := range adnResponse.AdUnits { + + if len(adunit.Ads) > 0 { + + ad := adunit.Ads[0] + + currency = ad.Bid.Currency + + creativeWidth, widthErr := strconv.ParseInt(ad.CreativeWidth, 10, 64) + if widthErr != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Value of width: %s is not a string", ad.CreativeWidth), + }} + } + + creativeHeight, heightErr := strconv.ParseInt(ad.CreativeHeight, 10, 64) + if heightErr != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Value of height: %s is not a string", ad.CreativeHeight), + }} + } + + adDomain := []string{} + for _, url := range ad.DestinationUrls { + domainArray := strings.Split(url, "/") + domain := strings.Replace(domainArray[2], "www.", "", -1) + adDomain = append(adDomain, domain) + } + + bid := openrtb2.Bid{ + ID: ad.AdId, + ImpID: request.Imp[i].ID, + W: creativeWidth, + H: creativeHeight, + AdID: ad.AdId, + CID: ad.LineItemId, + CrID: ad.CreativeId, + Price: ad.Bid.Amount * 1000, + AdM: adunit.Html, + ADomain: adDomain, + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: "banner", + }) + } + + } + bidResponse.Currency = currency + return bidResponse, nil +} diff --git a/adapters/adnuntius/adnuntius_test.go b/adapters/adnuntius/adnuntius_test.go new file mode 100644 index 00000000000..270f27a9366 --- /dev/null +++ b/adapters/adnuntius/adnuntius_test.go @@ -0,0 +1,46 @@ +package adnuntius + +import ( + "testing" + "time" + + "github.com/prebid/prebid-server/adapters" + "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.BidderAdnuntius, config.Adapter{ + Endpoint: "http://whatever.url"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + assertTzo(t, bidder) + replaceRealTimeWithKnownTime(bidder) + + adapterstest.RunJSONBidderTest(t, "adnuntiustest", bidder) +} + +func assertTzo(t *testing.T, bidder adapters.Bidder) { + bidderAdnuntius, _ := bidder.(*adapter) + assert.NotNil(t, bidderAdnuntius.time) +} + +// FakeTime implements the Time interface +type FakeTime struct { + time time.Time +} + +func (ft *FakeTime) Now() time.Time { + return ft.time +} + +func replaceRealTimeWithKnownTime(bidder adapters.Bidder) { + bidderAdnuntius, _ := bidder.(*adapter) + bidderAdnuntius.time = &FakeTime{ + time: time.Date(2016, 1, 1, 12, 30, 15, 0, time.UTC), + } +} diff --git a/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json new file mode 100644 index 00000000000..6e943810aa9 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "context": "prebid.org", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": ["google.com"], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json new file mode 100644 index 00000000000..e195429cef9 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl", + "ext": { + "consent": "CONSENT_STRING" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?consentString=CONSENT_STRING&format=json&gdpr=1&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": ["google.com"], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json b/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json new file mode 100644 index 00000000000..8305bcdb59a --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json @@ -0,0 +1,101 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "user": { + "id": "1kjh3429kjh295jkl" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "metaData": { + "usi": "1kjh3429kjh295jkl" + }, + "context": "unknown" + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-imp-id", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": ["google.com"], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json new file mode 100644 index 00000000000..ba388f1deea --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "context": "prebid.org", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "abc", + "creativeHeight": 240, + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field .AdUnits.Ads.CreativeHeight of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/native-error.json b/adapters/adnuntius/adnuntiustest/supplemental/native-error.json new file mode 100644 index 00000000000..a8203e581ef --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/native-error.json @@ -0,0 +1,25 @@ +{ + "mockBidRequest": { + "id": "unsupported-native-request", + "imp": [ + { + "id": "unsupported-native-imp", + "native": { + "ver": "1.1" + }, + "ext": { + "bidder": { + "auId": "1" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "ignoring imp id=unsupported-native-imp, Adnuntius supports only Banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/status-400.json b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json new file mode 100644 index 00000000000..c6da39dadd5 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json @@ -0,0 +1,54 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auI": "1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "", + "targetId": "-test-imp-id" + } + ], + "context": "unknown", + "metaData": {} + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Status code: 400, Request malformed", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json b/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json new file mode 100644 index 00000000000..727cf19498f --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-network", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123", + "network": "test" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-network" + } + ], + "context": "unknown", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": "980", + "creativeHeight": "240", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "adn-id-1559784094", + "impid": "test-network", + "price": 20000, + "adm": "", + "adid": "adn-id-1559784094", + "adomain": ["google.com"], + "cid": "q7y9qm5b0xt9htrv", + "crid": "jn9hpzvlsf8cpdmm", + "w": 980, + "h": 240 + }, + "type": "banner" + } + ], + "currency": "NOK" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/video-error.json b/adapters/adnuntius/adnuntiustest/supplemental/video-error.json new file mode 100644 index 00000000000..357cb165f36 --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/video-error.json @@ -0,0 +1,26 @@ +{ + "mockBidRequest": { + "id": "unsupported-native-request", + "imp": [ + { + "id": "unsupported-native-imp", + "video": { + "w": 728, + "h": 90 + }, + "ext": { + "bidder": { + "auId": "1" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "ignoring imp id=unsupported-native-imp, Adnuntius supports only Banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json new file mode 100644 index 00000000000..2659841197f --- /dev/null +++ b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "auId": "123" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://whatever.url?format=json&tzo=0", + "body": { + "adUnits": [ + { + "auId": "123", + "targetId": "123-test-imp-id" + } + ], + "context": "prebid.org", + "metaData": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "adUnits": [ + { + "auId": "0000000000000123", + "targetId": "123-test-imp-id", + "html": "", + "responseId": "adn-rsp-900646517", + "ads": [ + { + "destinationUrls": { + "url": "http://www.google.com" + }, + "bid": { + "amount": 20.0, + "currency": "NOK" + }, + "adId": "adn-id-1559784094", + "creativeWidth": 980, + "creativeHeight": "abc", + "creativeId": "jn9hpzvlsf8cpdmm", + "lineItemId": "q7y9qm5b0xt9htrv" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal number into Go struct field .AdUnits.Ads.CreativeWidth of type string", + "comparison": "literal" + } + ] +} diff --git a/adapters/adnuntius/params_test.go b/adapters/adnuntius/params_test.go new file mode 100644 index 00000000000..dd8fafc79a9 --- /dev/null +++ b/adapters/adnuntius/params_test.go @@ -0,0 +1,60 @@ +package adnuntius + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/adnuntius.json +// These also validate the format of the external API: request.imp[i].ext.adnuntius +// TestValidParams makes sure that the adnuntius 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.BidderAdnuntius, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adnuntius params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the adnuntius 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.BidderAdnuntius, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"auId":"123"}`, + `{"auId":"123", "network":"test"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"auId":123}`, + `{"auID":"123"}`, + `{"network":123}`, + `{"network":123, "auID":123}`, + `{"network":"test", "auID":123}`, + `{"network":test, "auID":"123"}`, +} diff --git a/config/config.go b/config/config.go index fd5f5e5bd15..49ca9589d17 100644 --- a/config/config.go +++ b/config/config.go @@ -771,6 +771,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adman.endpoint", "http://pub.admanmedia.com/?c=o&m=ortb") v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx") v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}") + v.SetDefault("adapters.adnuntius.endpoint", "https://ads.adnuntius.delivery/i") v.SetDefault("adapters.adoppler.endpoint", "http://{{.AccountID}}.trustedmarketplace.io/ads/processHeaderBid/{{.AdUnit}}") v.SetDefault("adapters.adot.endpoint", "https://dsp.adotmob.com/headerbidding/bidrequest") v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 70e6e18befe..db55e733ee4 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -13,6 +13,7 @@ import ( "github.com/prebid/prebid-server/adapters/adkernelAdn" "github.com/prebid/prebid-server/adapters/adman" "github.com/prebid/prebid-server/adapters/admixer" + "github.com/prebid/prebid-server/adapters/adnuntius" "github.com/prebid/prebid-server/adapters/adocean" "github.com/prebid/prebid-server/adapters/adoppler" "github.com/prebid/prebid-server/adapters/adot" @@ -148,6 +149,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAdkernelAdn: adkernelAdn.Builder, openrtb_ext.BidderAdman: adman.Builder, openrtb_ext.BidderAdmixer: admixer.Builder, + openrtb_ext.BidderAdnuntius: adnuntius.Builder, openrtb_ext.BidderAdOcean: adocean.Builder, openrtb_ext.BidderAdoppler: adoppler.Builder, openrtb_ext.BidderAdpone: adpone.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 427ff6f45b5..23ec7fdeaf5 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -83,6 +83,7 @@ const ( BidderAdkernelAdn BidderName = "adkernelAdn" BidderAdman BidderName = "adman" BidderAdmixer BidderName = "admixer" + BidderAdnuntius BidderName = "adnuntius" BidderAdOcean BidderName = "adocean" BidderAdoppler BidderName = "adoppler" BidderAdot BidderName = "adot" @@ -220,6 +221,7 @@ func CoreBidderNames() []BidderName { BidderAdkernelAdn, BidderAdman, BidderAdmixer, + BidderAdnuntius, BidderAdOcean, BidderAdoppler, BidderAdot, diff --git a/openrtb_ext/imp_adnuntius.go b/openrtb_ext/imp_adnuntius.go new file mode 100644 index 00000000000..bde30f0518f --- /dev/null +++ b/openrtb_ext/imp_adnuntius.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ImpExtAdnunitus struct { + Auid string `json:"auId"` + Network string `json:"network"` +} diff --git a/static/bidder-info/adnuntius.yaml b/static/bidder-info/adnuntius.yaml new file mode 100644 index 00000000000..86a0192cd62 --- /dev/null +++ b/static/bidder-info/adnuntius.yaml @@ -0,0 +1,10 @@ +maintainer: + email: hello@adnuntius.com +gvlVendorID: 855 +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner diff --git a/static/bidder-params/adnuntius.json b/static/bidder-params/adnuntius.json new file mode 100644 index 00000000000..a8e65bea343 --- /dev/null +++ b/static/bidder-params/adnuntius.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adnuntius Adapter Params", + "description": "A schema which validates params accepted by the Adnuntius adapter", + "type": "object", + + "properties": { + "auId": { + "type": "string", + "description": "Placement ID" + }, + "network": { + "type": "string", + "description": "Network if required" + } + }, + + "required": ["auId"] +} From 61bf35361c94edd190c47828d2afa7e53189be76 Mon Sep 17 00:00:00 2001 From: Jim Naumann Date: Wed, 3 Nov 2021 16:45:59 -0400 Subject: [PATCH 127/140] Beachfront - Currency stopgap (#2065) * Defined cookie sync URL in config, cleared deprecated comment in usersync * Update beachfront.md * editing documentation * box swap - isolating errors for json test responses * returns error on non-USD currency * moved supplimental tests out of noop Co-authored-by: jim Co-authored-by: Jim Naumann --- adapters/beachfront/beachfront.go | 26 +++++++++---- .../adm-video-unaccepted-currency.json | 38 +++++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-unaccepted-currency.json diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 8af61eb9a5a..3e3ff62bee0 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -269,10 +269,6 @@ func getSchain(request *openrtb2.BidRequest) (openrtb_ext.ExtRequestPrebidSChain return schain, json.Unmarshal(request.Source.Ext, &schain) } -/* -getBannerRequest, singular. A "Slot" is an "imp," and each Slot can have an AppId, so just one -request to the beachfront banner endpoint gets all banner Imps. -*/ func getBannerRequest(request *openrtb2.BidRequest) (beachfrontBannerRequest, []error) { var bfr beachfrontBannerRequest var errs = make([]error, 0, len(request.Imp)) @@ -294,7 +290,12 @@ func getBannerRequest(request *openrtb2.BidRequest) (beachfrontBannerRequest, [] continue } - setBidFloor(&beachfrontExt, &request.Imp[i]) + err = setBidFloor(&beachfrontExt, &request.Imp[i]) + + if err != nil { + errs = append(errs, err) + continue + } slot := beachfrontSlot{ Id: appid, @@ -467,7 +468,11 @@ func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, [ imp.Banner = nil imp.Ext = nil imp.Secure = &secure - setBidFloor(&beachfrontExt, &imp) + if err := setBidFloor(&beachfrontExt, &imp); err != nil { + errs = append(errs, err) + failedRequestIndicies = append(failedRequestIndicies, i) + continue + } if imp.Video.H == 0 && imp.Video.W == 0 { imp.Video.W = defaultVideoWidth @@ -563,7 +568,7 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb2.BidRequest, exter return bidResponse, errs } -func setBidFloor(ext *openrtb_ext.ExtImpBeachfront, imp *openrtb2.Imp) { +func setBidFloor(ext *openrtb_ext.ExtImpBeachfront, imp *openrtb2.Imp) error { var floor float64 if imp.BidFloor > 0 { @@ -574,11 +579,18 @@ func setBidFloor(ext *openrtb_ext.ExtImpBeachfront, imp *openrtb2.Imp) { floor = minBidFloor } + if floor >= 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != "USD" { + return &errortypes.BadInput{ + Message: fmt.Sprintf("unsupported bid currency, %s. bids are currently accepted in USD only.", imp.BidFloorCur), + } + } + if floor <= minBidFloor { floor = 0 } imp.BidFloor = floor + return nil } func (a *BeachfrontAdapter) getBidType(externalRequest *adapters.RequestData) openrtb_ext.BidType { diff --git a/adapters/beachfront/beachfronttest/supplemental/adm-video-unaccepted-currency.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-unaccepted-currency.json new file mode 100644 index 00000000000..c26b10aee51 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-unaccepted-currency.json @@ -0,0 +1,38 @@ +{ + "mockBidRequest": { + "id": "adm-video-unaccepted-curency", + "imp": [ + { + "id": "video1", + "bidfloorcur": "IRP", + "bidfloor": 695.61, + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "unsupported bid currency, IRP. bids are currently accepted in USD only.", + "comparison": "literal" + } + ] +} From e026a440675bcfc40d860272e7ba5500045fe5a6 Mon Sep 17 00:00:00 2001 From: Veronika Solovei Date: Thu, 4 Nov 2021 10:12:17 -0700 Subject: [PATCH 128/140] First party data (#1906) --- endpoints/openrtb2/auction.go | 13 +- endpoints/openrtb2/auction_test.go | 66 +- .../valid-fpd-allowed-with-ext-bidder.json | 50 - .../valid-fpd-allowed-with-prebid-bidder.json | 54 - .../supplementary/first-party-data.json | 156 ++ exchange/bidder_test.go | 2 +- exchange/exchange.go | 4 +- exchange/exchange_test.go | 99 +- exchange/utils.go | 17 + exchange/utils_test.go | 123 ++ firstpartydata/first_party_data.go | 641 ++++++++ firstpartydata/first_party_data_test.go | 1436 +++++++++++++++++ .../bidder-config-config-null.json | 20 + .../bidder-config-config-ortb-null.json | 22 + .../bidder-config-duplicated.json | 63 + .../bidder-config-empty.json | 11 + .../bidder-config-multiple-bidder.json | 109 ++ .../bidder-config-not-specified.json | 7 + .../bidder-config-null.json | 11 + .../bidder-list-specified-only.json | 15 + .../global-fpd-defined-app-content-data.json | 87 + .../global-fpd-defined-app.json | 80 + .../global-fpd-defined-data-user.json | 63 + .../global-fpd-defined-site-content-data.json | 96 ++ .../global-fpd-defined-site.json | 74 + .../global-fpd-empty-app-content-data.json | 58 + .../global-fpd-empty-app.json | 43 + .../global-fpd-empty-site-content-data.json | 64 + .../global-fpd-empty-site.json | 43 + .../global-fpd-empty-user-data.json | 42 + .../global-fpd-empty-user.json | 43 + ...obal-fpd-not-defined-app-content-data.json | 61 + .../global-fpd-not-defined-app.json | 39 + ...bal-fpd-not-defined-site-content-data.json | 67 + .../global-fpd-not-defined-site.json | 39 + .../global-fpd-not-defined-user-data.json | 45 + .../global-fpd-not-defined-user.json | 39 + .../two-bidders-correct-fpd.json | 98 ++ ...idders-global-and-non-global-app-user.json | 184 +++ ...dders-global-and-non-global-site-user.json | 186 +++ ...wo-bidders-global-and-non-global-user.json | 222 +++ .../two-bidders-no-global-bidder-list.json | 98 ++ .../two-bidders-one-with-incorrect-fpd.json | 54 + .../two-bidders-with-incorrect-fpd.json | 59 + .../tests/resolvefpd/bidder-fpd-only-app.json | 44 + .../resolvefpd/bidder-fpd-only-site.json | 44 + .../resolvefpd/bidder-fpd-only-user.json | 49 + ...bidder-fpd-app-content-data-user-data.json | 114 ++ .../resolvefpd/global-and-bidder-fpd-app.json | 80 + .../global-and-bidder-fpd-site-app-user.json | 130 ++ ...obal-and-bidder-fpd-site-content-data.json | 144 ++ .../global-and-bidder-fpd-site-user.json | 96 ++ .../global-and-bidder-fpd-site.json | 71 + .../global-and-bidder-fpd-user.json | 51 + .../global-and-bidder-site-content-data.json | 114 ++ .../tests/resolvefpd/req-app-not-defined.json | 46 + .../resolvefpd/req-site-not-defined.json | 33 + .../resolvefpd/req-user-not-defined.json | 31 + .../resolvefpd/site-page-empty-conflict.json | 45 + openrtb_ext/request.go | 17 + openrtb_ext/request_wrapper.go | 2 +- util/jsonutil/jsonutil.go | 92 +- util/jsonutil/jsonutil_test.go | 289 +--- 63 files changed, 5911 insertions(+), 384 deletions(-) delete mode 100644 endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json delete mode 100644 endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/first-party-data.json create mode 100644 firstpartydata/first_party_data.go create mode 100644 firstpartydata/first_party_data_test.go create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-null.json create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-ortb-null.json create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated.json create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-empty.json create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-multiple-bidder.json create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-not-specified.json create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-config-null.json create mode 100644 firstpartydata/tests/extractbidderconfigfpd/bidder-list-specified-only.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-defined-data-user.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site-content-data.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app-content-data.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site-content-data.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user-data.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app-content-data.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site-content-data.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user-data.json create mode 100644 firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user.json create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-correct-fpd.json create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-user.json create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-user.json create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-user.json create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-no-global-bidder-list.json create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-one-with-incorrect-fpd.json create mode 100644 firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json create mode 100644 firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json create mode 100644 firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json create mode 100644 firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json create mode 100644 firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json create mode 100644 firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json create mode 100644 firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-app-user.json create mode 100644 firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-content-data.json create mode 100644 firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json create mode 100644 firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json create mode 100644 firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json create mode 100644 firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json create mode 100644 firstpartydata/tests/resolvefpd/req-app-not-defined.json create mode 100644 firstpartydata/tests/resolvefpd/req-site-not-defined.json create mode 100644 firstpartydata/tests/resolvefpd/req-user-not-defined.json create mode 100644 firstpartydata/tests/resolvefpd/site-page-empty-conflict.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index eb6f17f2359..cd383c25179 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/firstpartydata" "io" "io/ioutil" "net/http" @@ -14,7 +15,7 @@ import ( "time" "github.com/buger/jsonparser" - jsonpatch "github.com/evanphx/json-patch" + "github.com/evanphx/json-patch" "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -140,10 +141,17 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http }() req, impExtInfoMap, errL := deps.parseRequest(r) - if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { return } + + resolvedFPD, fpdErrors := firstpartydata.ExtractFPDForBidders(req) + if len(fpdErrors) > 0 { + if errortypes.ContainsFatalError(fpdErrors) && writeError(fpdErrors, w, &labels) { + return + } + errL = append(errL, fpdErrors...) + } warnings := errortypes.WarningOnly(errL) ctx := context.Background() @@ -197,6 +205,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http Warnings: warnings, GlobalPrivacyControlHeader: secGPC, ImpExtInfoMap: impExtInfoMap, + FirstPartyData: resolvedFPD, } response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 20fdd56e74b..5b234b12c8b 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/firstpartydata" "io" "io/ioutil" "net" @@ -104,10 +105,6 @@ func TestJsonSampleRequests(t *testing.T) { "There are both disabled and non-disabled bidders, we expect a 200", "disabled/good", }, - { - "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", @@ -2510,6 +2507,56 @@ func TestParseRequestParseImpInfoError(t *testing.T) { assert.Contains(t, errL[0].Error(), "echovideoattrs of type bool", "Incorrect error message") } +func TestAuctionFirstPartyData(t *testing.T) { + reqBody := validRequest(t, "first-party-data.json") + deps := &endpointDeps{ + fakeUUIDGenerator{}, + &mockExchangeFPD{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(len(reqBody))}, + &metricsConfig.NilMetricsEngine{}, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps.Auction(recorder, req, nil) + + if recorder.Code != http.StatusOK { + t.Errorf("Endpoint should return a 200") + } + resultRequest := deps.ex.(*mockExchangeFPD).lastRequest + resultFPD := deps.ex.(*mockExchangeFPD).firstPartyData + + assert.Len(t, resultFPD, 2, "Result FPD length is incorrect") + + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder1")], "Result FPD for bidder1 is incorrect") + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder1")].Site, "Result FPD for bidder1.Site is incorrect") + assert.Nil(t, resultFPD[openrtb_ext.BidderName("bidder1")].App, "Result FPD for bidder1.App is incorrect") + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder1")].User, "Result FPD for bidder1.User is incorrect") + + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder2")], "Result FPD for bidder2 is incorrect") + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder2")].Site, "Result FPD for bidder2.Site is incorrect") + assert.Nil(t, resultFPD[openrtb_ext.BidderName("bidder2")].App, "Result FPD for bidder2.App is incorrect") + assert.NotNil(t, resultFPD[openrtb_ext.BidderName("bidder2")].User, "Result FPD for bidder2.User is incorrect") + + assert.Nil(t, resultRequest.App, "Result request App should be nil") + assert.Nil(t, resultRequest.Site.Content.Data, "Result request Site.Content.Data is incorrect") + assert.JSONEq(t, string(resultRequest.Site.Ext), `{"amp": 1}`, "Result request Site.Ext is incorrect") + assert.Nil(t, resultRequest.User.Ext, "Result request User.Ext is incorrect") +} + func TestValidateNativeContextTypes(t *testing.T) { impIndex := 4 @@ -3739,6 +3786,17 @@ func (m *mockExchange) HoldAuction(ctx context.Context, r exchange.AuctionReques }, nil } +type mockExchangeFPD struct { + lastRequest *openrtb2.BidRequest + firstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData +} + +func (m *mockExchangeFPD) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { + m.lastRequest = r.BidRequest + m.firstPartyData = r.FirstPartyData + return &openrtb2.BidResponse{}, nil +} + func getBidderInfos(cfg map[string]config.Adapter, biddersNames []openrtb_ext.BidderName) config.BidderInfos { biddersInfos := make(config.BidderInfos) for _, name := range biddersNames { 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 deleted file mode 100644 index a4b716b2040..00000000000 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", - - "mockBidRequest": { - "id": "some-request-id", - "site": { - "page": "test.somepage.com" - }, - "imp": [{ - "id": "some-imp-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - }, - "data": { - "keywords": "prebid server example" - }, - "context": { - "data": { - "keywords": "another prebid server example" - } - } - } - }] - }, - "expectedBidResponse": { - "id": "some-request-id", - "seatbid": [{ - "bid": [{ - "id": "appnexus-bid", - "impid": "", - "price": 0 - }], - "seat": "appnexus-bids" - }], - "bidid": "test bid id", - "cur": "USD", - "nbr": 0 - }, - "expectedReturnCode": 200 -} 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 deleted file mode 100644 index 27e8c46d9d7..00000000000 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", - - "mockBidRequest": { - "id": "some-request-id", - "site": { - "page": "test.somepage.com" - }, - "imp": [{ - "id": "some-imp-id", - "banner": { - "format": [{ - "w": 600, - "h": 500 - }, { - "w": 300, - "h": 600 - }] - }, - "ext": { - "prebid": { - "bidder": { - "appnexus": { - "placementId": 12883451 - } - } - }, - "data": { - "keywords": "prebid server example" - }, - "context": { - "data": { - "keywords": "another prebid server example" - } - } - } - }] - }, - "expectedBidResponse": { - "id": "some-request-id", - "seatbid": [{ - "bid": [{ - "id": "appnexus-bid", - "impid": "", - "price": 0 - }], - "seat": "appnexus-bids" - }], - "bidid": "test bid id", - "cur": "USD", - "nbr": 0 - }, - "expectedReturnCode": 200 -} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/first-party-data.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/first-party-data.json new file mode 100644 index 00000000000..4552b883b10 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/first-party-data.json @@ -0,0 +1,156 @@ +{ + "description": "Valid request with First party data for two bidders, both have global and bidder specific fpd", + "mockBidRequest": { + "id": "bid_req_id", + "imp": [ + { + "id": "1_0", + "video": { + "mimes": [ + "video/mp4" + ], + "maxduration": 30, + "protocols": [ + 2, + 3 + ], + "w": 640, + "h": 480 + }, + "ext": { + "appnexus": { + "placementId": 123 + }, + "telaria": { + "seatCode": "test", + "adCode": "test" + } + } + } + ], + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "reqContentDataSiteId1", + "name": "reqContentDataSiteName1" + } + ] + }, + "ext": { + "amp": 1, + "data": { + "somesitefpd": "fpdSite" + } + } + }, + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "reqDataUserId1", + "name": "reqDataUserName1" + }, + { + "id": "reqDataUserId2", + "name": "reqDataUserName2" + } + ], + "ext": { + "data": { + "moreuserdata": "morefpduserdata" + } + } + }, + "test": 1, + "at": 1, + "tmax": 5000, + "ext": { + "prebid": { + "data": { + "bidders": [ + "bidder1", + "bidder2" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "bidder1" + ], + "config": { + "ortb2": { + "site": { + "id": "fpdSiteId", + "keywords": "fpd keywords!", + "data": [ + { + "id": "siteId1", + "name": true + }, + { + "id": "siteId2", + "name": false + } + ], + "page": "", + "ext": { + "data": { + "morefpdData": "morefpddata", + "sitefpddata": "mysitefpddata" + } + } + } + } + } + }, + { + "bidders": [ + "bidder2" + ], + "config": { + "ortb2": { + "user": { + "id": "fpdUserId", + "yob": 2011, + "gender": "F", + "keywords": "fpd keywords", + "data": [ + { + "id": "fpdUserDataId1", + "name": "fpdUserDataName1" + }, + { + "id": "fpdUserDataId2", + "name": "fpdUserDataName2" + } + ], + "ext": { + "data": { + "userdata": "biddermyfpduserdata" + } + } + } + } + } + } + ] + } + } + }, + "expectedBidResponse": {}, + "expectedReturnCode": 200 +} diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 82f058514f7..401c245da47 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -1301,7 +1301,7 @@ func TestMobileNativeTypes(t *testing.T) { var actualValue string for _, bid := range seatBids.bids { actualValue = bid.bid.AdM - diffJson(t, tc.description, []byte(actualValue), []byte(tc.expectedValue)) + assert.JSONEq(t, tc.expectedValue, actualValue, tc.description) } } } diff --git a/exchange/exchange.go b/exchange/exchange.go index 8b4b49e74f2..6298d88a0f6 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -18,6 +18,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/firstpartydata" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" @@ -159,7 +160,8 @@ type AuctionRequest struct { // LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. - LegacyLabels metrics.Labels + LegacyLabels metrics.Labels + FirstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData } // BidderRequest holds the bidder specific request and all other diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index b4fd270d023..c7429dffafc 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/firstpartydata" "io/ioutil" "net/http" "net/http/httptest" @@ -34,7 +35,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/yudai/gojsondiff" - "github.com/yudai/gojsondiff/formatter" ) func TestNewExchange(t *testing.T) { @@ -377,7 +377,7 @@ func TestDebugBehaviour(t *testing.T) { // If not nil, assert bid extension if test.in.debug { - diffJson(t, test.desc, bidRequest.Ext, actualExt.Debug.ResolvedRequest.Ext) + assert.JSONEq(t, string(bidRequest.Ext), string(actualExt.Debug.ResolvedRequest.Ext), test.desc) } } else if !test.debugData.bidderLevelDebugAllowed && test.debugData.accountLevelDebugAllowed { assert.Equal(t, len(actualExt.Debug.HttpCalls), 0, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") @@ -2143,7 +2143,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { } if spec.IncomingRequest.OrtbRequest.Test == 1 { //compare debug info - diffJson(t, "Debug info modified", bid.Ext, spec.Response.Ext) + assert.JSONEq(t, string(bid.Ext), string(spec.Response.Ext), "Debug info modified") } } @@ -3613,6 +3613,60 @@ func TestMakeBidExtJSON(t *testing.T) { } } +func TestFPD(t *testing.T) { + + bidRequest := &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + Video: &openrtb2.Video{W: 100, H: 50}, + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), + }}, + Site: &openrtb2.Site{ID: "Req site id", Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + AT: 1, + TMax: 500, + } + + fpd := make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData) + + apnFpd := firstpartydata.ResolvedFirstPartyData{ + Site: &openrtb2.Site{ID: "fpdSite"}, + App: &openrtb2.App{ID: "fpdApp"}, + User: &openrtb2.User{ID: "fpdUser"}, + } + fpd[openrtb_ext.BidderName("appnexus")] = &apnFpd + + e := new(exchange) + e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderAppnexus: &capturingRequestBidder{}, + } + e.me = &metricsConf.NilMetricsEngine{} + e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + + ctx := context.Background() + + auctionRequest := AuctionRequest{ + BidRequest: bidRequest, + UserSyncs: &emptyUsersync{}, + FirstPartyData: fpd, + } + + debugLog := &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true} + + outBidResponse, err := e.HoldAuction(ctx, auctionRequest, debugLog) + + assert.NotNilf(t, outBidResponse, "outBidResponse should not be nil") + assert.Nil(t, err, "Error should be nil") + + request := e.adapterMap[openrtb_ext.BidderAppnexus].(*capturingRequestBidder).req + + assert.NotNil(t, request, "Bidder request should not be nil") + assert.Equal(t, apnFpd.Site, request.Site, "Site is incorrect") + assert.Equal(t, apnFpd.App, request.App, "App is incorrect") + assert.Equal(t, apnFpd.User, request.User, "User is incorrect") + +} + type exchangeSpec struct { GDPREnabled bool `json:"gdpr_enabled"` IncomingRequest exchangeRequest `json:"incomingRequest"` @@ -3733,6 +3787,15 @@ func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.Bid return } +type capturingRequestBidder struct { + req *openrtb2.BidRequest +} + +func (b *capturingRequestBidder) 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) { + b.req = request + return &pbsOrtbSeatBid{}, nil +} + func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) { t.Helper() actualJSON, err := json.Marshal(actual) @@ -3745,7 +3808,7 @@ func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRe t.Fatalf("%s failed to marshal expected BidRequest into JSON. %v", description, err) } - diffJson(t, description, actualJSON, expectedJSON) + assert.JSONEq(t, string(expectedJSON), string(actualJSON), description) } func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidResponse, actual *openrtb2.BidResponse) { @@ -3768,7 +3831,7 @@ func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidR t.Fatalf("%s failed to marshal expected BidResponse into JSON. %v", description, err) } - diffJson(t, description, actualJSON, expectedJSON) + assert.JSONEq(t, string(expectedJSON), string(actualJSON), description) } func mapifySeatBids(t *testing.T, context string, seatBids []openrtb2.SeatBid) map[string]*openrtb2.SeatBid { @@ -3784,32 +3847,6 @@ func mapifySeatBids(t *testing.T, context string, seatBids []openrtb2.SeatBid) m return seatMap } -// diffJson compares two JSON byte arrays for structural equality. It will produce an error if either -// byte array is not actually JSON. -func diffJson(t *testing.T, description string, actual []byte, expected []byte) { - t.Helper() - diff, err := gojsondiff.New().Compare(actual, expected) - if err != nil { - t.Fatalf("%s json diff failed. %v", description, err) - } - - if diff.Modified() { - var left interface{} - if err := json.Unmarshal(actual, &left); err != nil { - t.Fatalf("%s json did not match, but unmarshalling failed. %v", description, err) - } - printer := formatter.NewAsciiFormatter(left, formatter.AsciiFormatterConfig{ - ShowArrayIndex: true, - }) - output, err := printer.Format(diff) - if err != nil { - t.Errorf("%s did not match, but diff formatting failed. %v", description, err) - } else { - t.Errorf("%s json did not match expected.\n\n%s", description, output) - } - } -} - func mockHandler(statusCode int, getBody string, postBody string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(statusCode) diff --git a/exchange/utils.go b/exchange/utils.go index 59cc7f1e40a..5a68ba45df9 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -12,6 +12,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/firstpartydata" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" @@ -154,6 +155,10 @@ func cleanOpenRTBRequests(ctx context.Context, } } + if req.FirstPartyData != nil && req.FirstPartyData[bidderRequest.BidderName] != nil { + applyFPD(req.FirstPartyData[bidderRequest.BidderName], bidderRequest.BidRequest) + } + if bidRequestAllowed { privacyEnforcement.Apply(bidderRequest.BidRequest) allowedBidderRequests = append(allowedBidderRequests, bidderRequest) @@ -760,3 +765,15 @@ func writeNameVersionRecord(sb *strings.Builder, name, version string) { sb.WriteString("/") sb.WriteString(version) } + +func applyFPD(fpd *firstpartydata.ResolvedFirstPartyData, bidReq *openrtb2.BidRequest) { + if fpd.Site != nil { + bidReq.Site = fpd.Site + } + if fpd.App != nil { + bidReq.App = fpd.App + } + if fpd.User != nil { + bidReq.User = fpd.User + } +} diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 6360fc3b9d8..ef7d0bdb4a1 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/firstpartydata" "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -490,6 +491,70 @@ func TestCleanOpenRTBRequests(t *testing.T) { } } +func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { + fpd := make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData) + + apnFpd := firstpartydata.ResolvedFirstPartyData{ + Site: &openrtb2.Site{Name: "fpdApnSite"}, + App: &openrtb2.App{Name: "fpdApnApp"}, + User: &openrtb2.User{Keywords: "fpdApnUser"}, + } + fpd[openrtb_ext.BidderName("appnexus")] = &apnFpd + + brightrollFpd := firstpartydata.ResolvedFirstPartyData{ + Site: &openrtb2.Site{Name: "fpdBrightrollSite"}, + App: &openrtb2.App{Name: "fpdBrightrollApp"}, + User: &openrtb2.User{Keywords: "fpdBrightrollUser"}, + } + fpd[openrtb_ext.BidderName("brightroll")] = &brightrollFpd + + testCases := []struct { + description string + req AuctionRequest + fpdExpected bool + }{ + { + description: "Pass valid FPD data for bidder not found in the request", + req: AuctionRequest{BidRequest: getTestBuildRequest(t), UserSyncs: &emptyUsersync{}, FirstPartyData: fpd}, + fpdExpected: false, + }, + { + description: "Pass valid FPD data for bidders specified in request", + req: AuctionRequest{BidRequest: newAdapterAliasBidRequest(t), UserSyncs: &emptyUsersync{}, FirstPartyData: fpd}, + fpdExpected: true, + }, + { + description: "Bidders specified in request but there is no fpd data for this bidder", + req: AuctionRequest{BidRequest: newAdapterAliasBidRequest(t), UserSyncs: &emptyUsersync{}, FirstPartyData: make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData)}, + fpdExpected: false, + }, + { + description: "No FPD data passed", + req: AuctionRequest{BidRequest: newAdapterAliasBidRequest(t), UserSyncs: &emptyUsersync{}, FirstPartyData: nil}, + fpdExpected: false, + }, + } + + for _, test := range testCases { + metricsMock := metrics.MetricsEngineMock{} + bidderToSyncerKey := map[string]string{} + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &permissions, &metricsMock, gdpr.SignalNo, config.Privacy{}, nil) + assert.Empty(t, err, "No errors should be returned") + for _, bidderRequest := range bidderRequests { + bidderName := bidderRequest.BidderName + if test.fpdExpected { + assert.Equal(t, fpd[bidderName].Site.Name, bidderRequest.BidRequest.Site.Name, "Incorrect FPD site name") + assert.Equal(t, fpd[bidderName].App.Name, bidderRequest.BidRequest.App.Name, "Incorrect FPD app name") + assert.Equal(t, fpd[bidderName].User.Keywords, bidderRequest.BidRequest.User.Keywords, "Incorrect FPD user keywords") + } else { + assert.Equal(t, "", bidderRequest.BidRequest.Site.Name, "Incorrect FPD site name") + assert.Equal(t, "", bidderRequest.BidRequest.User.Keywords, "Incorrect FPD user keywords") + } + } + } +} + func TestCleanOpenRTBRequestsCCPA(t *testing.T) { trueValue, falseValue := true, false @@ -2543,3 +2608,61 @@ func TestBuildXPrebidHeader(t *testing.T) { assert.Equal(t, test.result, result, test.description+":result") } } + +func TestApplyFPD(t *testing.T) { + + fpdBidderTest := map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{} + bidderTest := openrtb_ext.BidderName("test") + fpdBidderTest[bidderTest] = &firstpartydata.ResolvedFirstPartyData{Site: nil, App: nil, User: nil} + + fpdBidderNotNilFPD := map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{} + bidderNotNilFPD := openrtb_ext.BidderName("notNilFPD") + fpdBidderNotNilFPD[bidderNotNilFPD] = &firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}} + + fpdBidderApp := map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{} + bidderApp := openrtb_ext.BidderName("AppFPD") + fpdBidderApp[bidderApp] = &firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}} + + testCases := []struct { + description string + inputFpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData + bidderName openrtb_ext.BidderName + inputRequest openrtb2.BidRequest + expectedRequest openrtb2.BidRequest + }{ + { + description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same", + inputFpd: fpdBidderTest, + bidderName: bidderTest, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + }, + { + description: "req.Site, req.App, req.User are not defined; bidderFPD.App, bidderFPD.Site and bidderFPD.User defined; " + + "expect req.Site, req.App, req.User to be overriden by bidderFPD.App, bidderFPD.Site and bidderFPD.User", + inputFpd: fpdBidderNotNilFPD, + bidderName: bidderNotNilFPD, + inputRequest: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, + }, + { + description: "req.Site, defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App; expect req.Site remains the same", + inputFpd: fpdBidderApp, + bidderName: bidderApp, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + }, + { + description: "req.Site, req.App defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App", + inputFpd: fpdBidderApp, + bidderName: bidderApp, + inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "TestAppId"}}, + expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, + }, + } + + for _, testCase := range testCases { + applyFPD(testCase.inputFpd[testCase.bidderName], &testCase.inputRequest) + assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description)) + } +} diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go new file mode 100644 index 00000000000..dc5b6f318e0 --- /dev/null +++ b/firstpartydata/first_party_data.go @@ -0,0 +1,641 @@ +package firstpartydata + +import ( + "encoding/json" + "fmt" + "github.com/evanphx/json-patch" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const ( + siteKey = "site" + appKey = "app" + userKey = "user" + dataKey = "data" + extKey = "ext" + + userDataKey = "userData" + appContentDataKey = "appContentData" + siteContentDataKey = "siteContentData" + + keywordsKey = "keywords" + genderKey = "gender" + yobKey = "yob" + pageKey = "page" + nameKey = "name" + domainKey = "domain" + catKey = "cat" + sectionCatKey = "sectioncat" + pageCatKey = "pagecat" + searchKey = "search" + refKey = "ref" + bundleKey = "bundle" + storeUrlKey = "storeurl" + verKey = "ver" +) + +type ResolvedFirstPartyData struct { + Site *openrtb2.Site + App *openrtb2.App + User *openrtb2.User +} + +// ExtractGlobalFPD extracts request level FPD from the request and removes req.{site,app,user}.ext.data if exists +func ExtractGlobalFPD(req *openrtb_ext.RequestWrapper) (map[string][]byte, error) { + + fpdReqData := make(map[string][]byte, 3) + + siteExt, err := req.GetSiteExt() + if err != nil { + return nil, err + } + refreshExt := false + + if len(siteExt.GetExt()[dataKey]) > 0 { + newSiteExt := siteExt.GetExt() + fpdReqData[siteKey] = newSiteExt[dataKey] + delete(newSiteExt, dataKey) + siteExt.SetExt(newSiteExt) + refreshExt = true + } + + appExt, err := req.GetAppExt() + if err != nil { + return nil, err + } + if len(appExt.GetExt()[dataKey]) > 0 { + newAppExt := appExt.GetExt() + fpdReqData[appKey] = newAppExt[dataKey] + delete(newAppExt, dataKey) + appExt.SetExt(newAppExt) + refreshExt = true + } + + userExt, err := req.GetUserExt() + if err != nil { + return nil, err + } + if len(userExt.GetExt()[dataKey]) > 0 { + newUserExt := userExt.GetExt() + fpdReqData[userKey] = newUserExt[dataKey] + delete(newUserExt, dataKey) + userExt.SetExt(newUserExt) + refreshExt = true + } + if refreshExt { + //need to keep site/app/user ext clean in case bidder is not in global fpd bidder list + // rebuild/resync the request in the request wrapper. + if err := req.RebuildRequest(); err != nil { + return nil, err + } + } + + return fpdReqData, nil +} + +//ExtractOpenRtbGlobalFPD extracts and deletes user.data and {app/site}.content.data from request +func ExtractOpenRtbGlobalFPD(bidRequest *openrtb2.BidRequest) map[string][]openrtb2.Data { + + openRtbGlobalFPD := make(map[string][]openrtb2.Data, 3) + if bidRequest.User != nil && len(bidRequest.User.Data) > 0 { + openRtbGlobalFPD[userDataKey] = bidRequest.User.Data + bidRequest.User.Data = nil + } + + if bidRequest.Site != nil && bidRequest.Site.Content != nil && len(bidRequest.Site.Content.Data) > 0 { + openRtbGlobalFPD[siteContentDataKey] = bidRequest.Site.Content.Data + bidRequest.Site.Content.Data = nil + } + + if bidRequest.App != nil && bidRequest.App.Content != nil && len(bidRequest.App.Content.Data) > 0 { + openRtbGlobalFPD[appContentDataKey] = bidRequest.App.Content.Data + bidRequest.App.Content.Data = nil + } + + return openRtbGlobalFPD + +} + +//ResolveFPD consolidates First Party Data from different sources and returns valid FPD that will be applied to bidders later or returns errors +func ResolveFPD(bidRequest *openrtb2.BidRequest, fpdBidderConfigData map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, biddersWithGlobalFPD []string) (map[openrtb_ext.BidderName]*ResolvedFirstPartyData, []error) { + var errL []error + + resolvedFpd := make(map[openrtb_ext.BidderName]*ResolvedFirstPartyData) + + allBiddersTable := make(map[string]struct{}) + + if biddersWithGlobalFPD == nil { + //add all bidders in bidder configs to receive global data and bidder specific data + for bidderName := range fpdBidderConfigData { + if _, present := allBiddersTable[string(bidderName)]; !present { + allBiddersTable[string(bidderName)] = struct{}{} + } + } + } else { + //only bidders in global bidder list will receive global data and bidder specific data + for _, bidderName := range biddersWithGlobalFPD { + if _, present := allBiddersTable[string(bidderName)]; !present { + allBiddersTable[string(bidderName)] = struct{}{} + } + } + } + + for bidderName := range allBiddersTable { + + fpdConfig := fpdBidderConfigData[openrtb_ext.BidderName(bidderName)] + + resolvedFpdConfig := &ResolvedFirstPartyData{} + + newUser, err := resolveUser(fpdConfig, bidRequest.User, globalFPD, openRtbGlobalFPD, bidderName) + if err != nil { + errL = append(errL, err) + } + resolvedFpdConfig.User = newUser + + newApp, err := resolveApp(fpdConfig, bidRequest.App, globalFPD, openRtbGlobalFPD, bidderName) + if err != nil { + errL = append(errL, err) + } + resolvedFpdConfig.App = newApp + + newSite, err := resolveSite(fpdConfig, bidRequest.Site, globalFPD, openRtbGlobalFPD, bidderName) + if err != nil { + errL = append(errL, err) + } + resolvedFpdConfig.Site = newSite + + if len(errL) == 0 { + resolvedFpd[openrtb_ext.BidderName(bidderName)] = resolvedFpdConfig + } + } + return resolvedFpd, errL +} + +func resolveUser(fpdConfig *openrtb_ext.ORTB2, bidRequestUser *openrtb2.User, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.User, error) { + var fpdConfigUser map[string]json.RawMessage + + if fpdConfig != nil && fpdConfig.User != nil { + fpdConfigUser = fpdConfig.User + } + + if bidRequestUser == nil && fpdConfigUser == nil { + return nil, nil + } + + if bidRequestUser == nil && fpdConfigUser != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("incorrect First Party Data for bidder %s: User object is not defined in request, but defined in FPD config", bidderName), + } + } + + newUser := *bidRequestUser + var err error + + //apply global fpd + if len(globalFPD[userKey]) > 0 { + extData := buildExtData(globalFPD[userKey]) + if len(newUser.Ext) > 0 { + newUser.Ext, err = jsonpatch.MergePatch(newUser.Ext, extData) + } else { + newUser.Ext = extData + } + } + if openRtbGlobalFPD != nil && len(openRtbGlobalFPD[userDataKey]) > 0 { + newUser.Data = openRtbGlobalFPD[userDataKey] + } + if fpdConfigUser != nil { + //apply bidder specific fpd if present + newUser, err = mergeUsers(&newUser, fpdConfigUser) + } + + return &newUser, err +} + +func unmarshalJSONToInt64(input json.RawMessage) (int64, error) { + var num json.Number + err := json.Unmarshal(input, &num) + if err != nil { + return -1, err + } + resNum, err := num.Int64() + return resNum, err +} + +func unmarshalJSONToString(input json.RawMessage) (string, error) { + var inputString string + err := json.Unmarshal(input, &inputString) + return inputString, err +} + +func unmarshalJSONToStringArray(input json.RawMessage) ([]string, error) { + var inputString []string + err := json.Unmarshal(input, &inputString) + return inputString, err +} + +//resolveExtension inserts remaining {site/app/user} attributes back to {site/app/user}.ext.data +func resolveExtension(fpdConfig map[string]json.RawMessage, originalExt json.RawMessage) ([]byte, error) { + resExt := originalExt + var err error + + if resExt == nil && len(fpdConfig) > 0 { + fpdExt, err := json.Marshal(fpdConfig) + return buildExtData(fpdExt), err + } + + fpdConfigExt, present := fpdConfig[extKey] + if present { + delete(fpdConfig, extKey) + resExt, err = jsonpatch.MergePatch(resExt, fpdConfigExt) + if err != nil { + return nil, err + } + } + + if len(fpdConfig) > 0 { + fpdData, err := json.Marshal(fpdConfig) + if err != nil { + return nil, err + } + data := buildExtData(fpdData) + return jsonpatch.MergePatch(resExt, data) + } + return resExt, nil +} + +func mergeUsers(original *openrtb2.User, fpdConfigUser map[string]json.RawMessage) (openrtb2.User, error) { + + var err error + newUser := *original + + if keywords, present := fpdConfigUser[keywordsKey]; present { + newUser.Keywords, err = unmarshalJSONToString(keywords) + if err != nil { + return newUser, err + } + delete(fpdConfigUser, keywordsKey) + } + if gender, present := fpdConfigUser[genderKey]; present { + newUser.Gender, err = unmarshalJSONToString(gender) + if err != nil { + return newUser, err + } + delete(fpdConfigUser, genderKey) + } + if yob, present := fpdConfigUser[yobKey]; present { + newUser.Yob, err = unmarshalJSONToInt64(yob) + if err != nil { + return newUser, err + } + delete(fpdConfigUser, yobKey) + } + + if len(fpdConfigUser) > 0 { + newUser.Ext, err = resolveExtension(fpdConfigUser, original.Ext) + } + + return newUser, err +} + +func resolveSite(fpdConfig *openrtb_ext.ORTB2, bidRequestSite *openrtb2.Site, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.Site, error) { + var fpdConfigSite map[string]json.RawMessage + + if fpdConfig != nil && fpdConfig.Site != nil { + fpdConfigSite = fpdConfig.Site + } + + if bidRequestSite == nil && fpdConfigSite == nil { + return nil, nil + } + if bidRequestSite == nil && fpdConfigSite != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("incorrect First Party Data for bidder %s: Site object is not defined in request, but defined in FPD config", bidderName), + } + } + + newSite := *bidRequestSite + var err error + + //apply global fpd + if len(globalFPD[siteKey]) > 0 { + extData := buildExtData(globalFPD[siteKey]) + if len(newSite.Ext) > 0 { + newSite.Ext, err = jsonpatch.MergePatch(newSite.Ext, extData) + } else { + newSite.Ext = extData + } + } + if openRtbGlobalFPD != nil && len(openRtbGlobalFPD[siteContentDataKey]) > 0 { + if newSite.Content != nil { + contentCopy := *newSite.Content + contentCopy.Data = openRtbGlobalFPD[siteContentDataKey] + newSite.Content = &contentCopy + } else { + newSite.Content = &openrtb2.Content{Data: openRtbGlobalFPD[siteContentDataKey]} + } + } + + if fpdConfigSite != nil { + newSite, err = mergeSites(&newSite, fpdConfigSite, bidderName) + } + return &newSite, err + +} +func mergeSites(originalSite *openrtb2.Site, fpdConfigSite map[string]json.RawMessage, bidderName string) (openrtb2.Site, error) { + + var err error + newSite := *originalSite + + if page, present := fpdConfigSite[pageKey]; present { + sitePage, err := unmarshalJSONToString(page) + if err != nil { + return newSite, err + } + //apply bidder specific fpd if present + //result site should have ID or Page, fpd becomes incorrect if it overwrites page to empty one and ID is empty in original site + if sitePage == "" && newSite.Page != "" && newSite.ID == "" { + return newSite, &errortypes.BadInput{ + Message: fmt.Sprintf("incorrect First Party Data for bidder %s: Site object cannot set empty page if req.site.id is empty", bidderName), + } + + } + newSite.Page = sitePage + delete(fpdConfigSite, pageKey) + } + if name, present := fpdConfigSite[nameKey]; present { + newSite.Name, err = unmarshalJSONToString(name) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, nameKey) + } + if domain, present := fpdConfigSite[domainKey]; present { + newSite.Domain, err = unmarshalJSONToString(domain) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, domainKey) + } + if cat, present := fpdConfigSite[catKey]; present { + newSite.Cat, err = unmarshalJSONToStringArray(cat) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, catKey) + } + if sectionCat, present := fpdConfigSite[sectionCatKey]; present { + newSite.SectionCat, err = unmarshalJSONToStringArray(sectionCat) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, sectionCatKey) + } + if pageCat, present := fpdConfigSite[pageCatKey]; present { + newSite.PageCat, err = unmarshalJSONToStringArray(pageCat) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, pageCatKey) + } + if search, present := fpdConfigSite[searchKey]; present { + newSite.Search, err = unmarshalJSONToString(search) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, searchKey) + } + if keywords, present := fpdConfigSite[keywordsKey]; present { + newSite.Keywords, err = unmarshalJSONToString(keywords) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, keywordsKey) + } + if ref, present := fpdConfigSite[refKey]; present { + newSite.Ref, err = unmarshalJSONToString(ref) + if err != nil { + return newSite, err + } + delete(fpdConfigSite, refKey) + } + + if len(fpdConfigSite) > 0 { + newSite.Ext, err = resolveExtension(fpdConfigSite, originalSite.Ext) + } + + return newSite, err +} + +func resolveApp(fpdConfig *openrtb_ext.ORTB2, bidRequestApp *openrtb2.App, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.App, error) { + + var fpdConfigApp map[string]json.RawMessage + + if fpdConfig != nil && fpdConfig.App != nil { + fpdConfigApp = fpdConfig.App + } + + if bidRequestApp == nil && fpdConfigApp == nil { + return nil, nil + } + + if bidRequestApp == nil && fpdConfigApp != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("incorrect First Party Data for bidder %s: App object is not defined in request, but defined in FPD config", bidderName), + } + } + + newApp := *bidRequestApp + var err error + + //apply global fpd if exists + if len(globalFPD[appKey]) > 0 { + extData := buildExtData(globalFPD[appKey]) + if len(newApp.Ext) > 0 { + newApp.Ext, err = jsonpatch.MergePatch(newApp.Ext, extData) + } else { + newApp.Ext = extData + } + } + if openRtbGlobalFPD != nil && len(openRtbGlobalFPD[appContentDataKey]) > 0 { + if newApp.Content != nil { + contentCopy := *newApp.Content + contentCopy.Data = openRtbGlobalFPD[appContentDataKey] + newApp.Content = &contentCopy + } else { + newApp.Content = &openrtb2.Content{Data: openRtbGlobalFPD[appContentDataKey]} + } + } + + if fpdConfigApp != nil { + //apply bidder specific fpd if present + newApp, err = mergeApps(&newApp, fpdConfigApp) + } + + return &newApp, err +} + +func mergeApps(originalApp *openrtb2.App, fpdConfigApp map[string]json.RawMessage) (openrtb2.App, error) { + + var err error + newApp := *originalApp + + if name, present := fpdConfigApp[nameKey]; present { + newApp.Name, err = unmarshalJSONToString(name) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, nameKey) + } + if bundle, present := fpdConfigApp[bundleKey]; present { + newApp.Bundle, err = unmarshalJSONToString(bundle) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, bundleKey) + } + if domain, present := fpdConfigApp[domainKey]; present { + newApp.Domain, err = unmarshalJSONToString(domain) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, domainKey) + } + if storeUrl, present := fpdConfigApp[storeUrlKey]; present { + newApp.StoreURL, err = unmarshalJSONToString(storeUrl) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, storeUrlKey) + } + if cat, present := fpdConfigApp[catKey]; present { + newApp.Cat, err = unmarshalJSONToStringArray(cat) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, catKey) + } + if sectionCat, present := fpdConfigApp[sectionCatKey]; present { + newApp.SectionCat, err = unmarshalJSONToStringArray(sectionCat) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, sectionCatKey) + } + if pageCat, present := fpdConfigApp[pageCatKey]; present { + newApp.PageCat, err = unmarshalJSONToStringArray(pageCat) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, pageCatKey) + } + if version, present := fpdConfigApp[verKey]; present { + newApp.Ver, err = unmarshalJSONToString(version) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, verKey) + } + if keywords, present := fpdConfigApp[keywordsKey]; present { + newApp.Keywords, err = unmarshalJSONToString(keywords) + if err != nil { + return newApp, err + } + delete(fpdConfigApp, keywordsKey) + } + + if len(fpdConfigApp) > 0 { + newApp.Ext, err = resolveExtension(fpdConfigApp, originalApp.Ext) + } + + return newApp, err +} + +func buildExtData(data []byte) []byte { + res := make([]byte, 0, len(data)) + res = append(res, []byte(`{"data":`)...) + res = append(res, data...) + res = append(res, []byte(`}`)...) + return res +} + +//ExtractBidderConfigFPD extracts bidder specific configs from req.ext.prebid.bidderconfig +func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, error) { + + fpd := make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2) + reqExtPrebid := reqExt.GetPrebid() + if reqExtPrebid != nil { + for _, bidderConfig := range reqExtPrebid.BidderConfigs { + for _, bidder := range bidderConfig.Bidders { + if _, present := fpd[openrtb_ext.BidderName(bidder)]; present { + //if bidder has duplicated config - throw an error + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("multiple First Party Data bidder configs provided for bidder: %s", bidder), + } + } + + fpdBidderData := &openrtb_ext.ORTB2{} + + if bidderConfig.Config != nil && bidderConfig.Config.ORTB2 != nil { + if bidderConfig.Config.ORTB2.Site != nil { + fpdBidderData.Site = bidderConfig.Config.ORTB2.Site + } + if bidderConfig.Config.ORTB2.App != nil { + fpdBidderData.App = bidderConfig.Config.ORTB2.App + } + if bidderConfig.Config.ORTB2.User != nil { + fpdBidderData.User = bidderConfig.Config.ORTB2.User + } + } + + fpd[openrtb_ext.BidderName(bidder)] = fpdBidderData + } + } + reqExtPrebid.BidderConfigs = nil + reqExt.SetPrebid(reqExtPrebid) + } + return fpd, nil + +} + +//ExtractFPDForBidders extracts FPD data from request if specified +func ExtractFPDForBidders(req *openrtb_ext.RequestWrapper) (map[openrtb_ext.BidderName]*ResolvedFirstPartyData, []error) { + + reqExt, err := req.GetRequestExt() + if err != nil { + return nil, []error{err} + } + if reqExt == nil || reqExt.GetPrebid() == nil { + return nil, nil + } + var biddersWithGlobalFPD []string + + extPrebid := reqExt.GetPrebid() + if extPrebid.Data != nil { + biddersWithGlobalFPD = extPrebid.Data.Bidders + extPrebid.Data.Bidders = nil + reqExt.SetPrebid(extPrebid) + } + + fbdBidderConfigData, err := ExtractBidderConfigFPD(reqExt) + if err != nil { + return nil, []error{err} + } + + var globalFpd map[string][]byte + var openRtbGlobalFPD map[string][]openrtb2.Data + + if biddersWithGlobalFPD != nil { + //global fpd data should not be extracted and removed from request if global bidder list is nil. + //Bidders that don't have any fpd config should receive request data as is + globalFpd, err = ExtractGlobalFPD(req) + if err != nil { + return nil, []error{err} + } + openRtbGlobalFPD = ExtractOpenRtbGlobalFPD(req.BidRequest) + } + + return ResolveFPD(req.BidRequest, fbdBidderConfigData, globalFpd, openRtbGlobalFPD, biddersWithGlobalFPD) + +} diff --git a/firstpartydata/first_party_data_test.go b/firstpartydata/first_party_data_test.go new file mode 100644 index 00000000000..1e09bab14e2 --- /dev/null +++ b/firstpartydata/first_party_data_test.go @@ -0,0 +1,1436 @@ +package firstpartydata + +import ( + "encoding/json" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + "io/ioutil" + "testing" +) + +func TestExtractGlobalFPD(t *testing.T) { + + testCases := []struct { + description string + input openrtb_ext.RequestWrapper + expectedReq openrtb_ext.RequestWrapper + expectedFpd map[string][]byte + }{ + { + description: "Site, app and user data present", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + Ext: json.RawMessage(`{"data": {"somesitefpd": "sitefpdDataTest"}}`), + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + Ext: json.RawMessage(`{"data": {"someuserfpd": "userfpdDataTest"}}`), + }, + App: &openrtb2.App{ + ID: "appId", + Ext: json.RawMessage(`{"data": {"someappfpd": "appfpdDataTest"}}`), + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + App: &openrtb2.App{ + ID: "appId", + }, + }}, + expectedFpd: map[string][]byte{ + "site": []byte(`{"somesitefpd": "sitefpdDataTest"}`), + "user": []byte(`{"someuserfpd": "userfpdDataTest"}`), + "app": []byte(`{"someappfpd": "appfpdDataTest"}`), + }, + }, + { + description: "App FPD only present", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + App: &openrtb2.App{ + ID: "appId", + Ext: json.RawMessage(`{"data": {"someappfpd": "appfpdDataTest"}}`), + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedFpd: map[string][]byte{ + "app": []byte(`{"someappfpd": "appfpdDataTest"}`), + "user": nil, + "site": nil, + }, + }, + { + description: "User FPD only present", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + Ext: json.RawMessage(`{"data": {"someuserfpd": "userfpdDataTest"}}`), + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + }, + }, + expectedFpd: map[string][]byte{ + "app": nil, + "user": []byte(`{"someuserfpd": "userfpdDataTest"}`), + "site": nil, + }, + }, + { + description: "No FPD present in req", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedFpd: map[string][]byte{ + "app": nil, + "user": nil, + "site": nil, + }, + }, + { + description: "Site FPD only present", + input: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + Ext: json.RawMessage(`{"data": {"someappfpd": true}}`), + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedReq: openrtb_ext.RequestWrapper{ + BidRequest: &openrtb2.BidRequest{ + ID: "bid_id", + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "http://www.foobar.com/1234.html", + Publisher: &openrtb2.Publisher{ + ID: "1", + }, + }, + App: &openrtb2.App{ + ID: "appId", + }, + }, + }, + expectedFpd: map[string][]byte{ + "app": nil, + "user": nil, + "site": []byte(`{"someappfpd": true}`), + }, + }, + } + for _, test := range testCases { + + inputReq := &test.input + fpd, err := ExtractGlobalFPD(inputReq) + assert.NoError(t, err, "Error should be nil") + err = inputReq.RebuildRequest() + assert.NoError(t, err, "Error should be nil") + + assert.Equal(t, test.expectedReq.BidRequest, inputReq.BidRequest, "Incorrect input request after global fpd extraction") + + assert.Equal(t, test.expectedFpd[userKey], fpd[userKey], "Incorrect User FPD") + assert.Equal(t, test.expectedFpd[appKey], fpd[appKey], "Incorrect App FPD") + assert.Equal(t, test.expectedFpd[siteKey], fpd[siteKey], "Incorrect Site FPDt") + } +} + +func TestExtractOpenRtbGlobalFPD(t *testing.T) { + + testCases := []struct { + description string + input openrtb2.BidRequest + output openrtb2.BidRequest + expectedFpdData map[string][]openrtb2.Data + }{ + { + description: "Site, app and user data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + Content: &openrtb2.Content{ + Data: []openrtb2.Data{ + {ID: "siteDataId1", Name: "siteDataName1"}, + {ID: "siteDataId2", Name: "siteDataName2"}, + }, + }, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + Data: []openrtb2.Data{ + {ID: "userDataId1", Name: "userDataName1"}, + }, + }, + App: &openrtb2.App{ + ID: "appId", + Content: &openrtb2.Content{ + Data: []openrtb2.Data{ + {ID: "appDataId1", Name: "appDataName1"}, + }, + }, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + Content: &openrtb2.Content{}, + }, + User: &openrtb2.User{ + ID: "reqUserID", + Yob: 1982, + Gender: "M", + }, + App: &openrtb2.App{ + ID: "appId", + Content: &openrtb2.Content{}, + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: {{ID: "siteDataId1", Name: "siteDataName1"}, {ID: "siteDataId2", Name: "siteDataName2"}}, + userDataKey: {{ID: "userDataId1", Name: "userDataName1"}}, + appContentDataKey: {{ID: "appDataId1", Name: "appDataName1"}}, + }, + }, + { + description: "No Site, app or user data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: nil, + userDataKey: nil, + appContentDataKey: nil, + }, + }, + { + description: "Site only data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "test/page", + Content: &openrtb2.Content{ + Data: []openrtb2.Data{ + {ID: "siteDataId1", Name: "siteDataName1"}, + }, + }, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + Page: "test/page", + Content: &openrtb2.Content{}, + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: {{ID: "siteDataId1", Name: "siteDataName1"}}, + userDataKey: nil, + appContentDataKey: nil, + }, + }, + { + description: "App only data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + App: &openrtb2.App{ + ID: "reqAppId", + Content: &openrtb2.Content{ + Data: []openrtb2.Data{ + {ID: "appDataId1", Name: "appDataName1"}, + }, + }, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + App: &openrtb2.App{ + ID: "reqAppId", + Content: &openrtb2.Content{}, + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: nil, + userDataKey: nil, + appContentDataKey: {{ID: "appDataId1", Name: "appDataName1"}}, + }, + }, + { + description: "User only data present", + input: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + }, + App: &openrtb2.App{ + ID: "reqAppId", + }, + User: &openrtb2.User{ + ID: "reqUserId", + Yob: 1982, + Gender: "M", + Data: []openrtb2.Data{ + {ID: "userDataId1", Name: "userDataName1"}, + }, + }, + }, + output: openrtb2.BidRequest{ + ID: "bid_id", + Imp: []openrtb2.Imp{ + {ID: "impid"}, + }, + Site: &openrtb2.Site{ + ID: "reqSiteId", + }, + App: &openrtb2.App{ + ID: "reqAppId", + }, + User: &openrtb2.User{ + ID: "reqUserId", + Yob: 1982, + Gender: "M", + }, + }, + expectedFpdData: map[string][]openrtb2.Data{ + siteContentDataKey: nil, + userDataKey: {{ID: "userDataId1", Name: "userDataName1"}}, + appContentDataKey: nil, + }, + }, + } + for _, test := range testCases { + + inputReq := &test.input + + res := ExtractOpenRtbGlobalFPD(inputReq) + + assert.Equal(t, &test.output, inputReq, "Result request is incorrect") + assert.Equal(t, test.expectedFpdData[siteContentDataKey], res[siteContentDataKey], "siteContentData data is incorrect") + assert.Equal(t, test.expectedFpdData[userDataKey], res[userDataKey], "userData is incorrect") + assert.Equal(t, test.expectedFpdData[appContentDataKey], res[appContentDataKey], "appContentData is incorrect") + + } +} + +func TestExtractBidderConfigFPD(t *testing.T) { + + if specFiles, err := ioutil.ReadDir("./tests/extractbidderconfigfpd"); err == nil { + for _, specFile := range specFiles { + fileName := "./tests/extractbidderconfigfpd/" + specFile.Name() + + fpdFile, err := loadFpdFile(fileName) + if err != nil { + t.Errorf("Unable to load file: %s", fileName) + } + var extReq openrtb_ext.ExtRequestPrebid + err = json.Unmarshal(fpdFile.InputRequestData, &extReq) + if err != nil { + t.Errorf("Unable to unmarshal input request: %s", fileName) + } + reqExt := openrtb_ext.RequestExt{} + reqExt.SetPrebid(&extReq) + fpdData, err := ExtractBidderConfigFPD(&reqExt) + + if len(fpdFile.ValidationErrors) > 0 { + assert.Equal(t, err.Error(), fpdFile.ValidationErrors[0].Message, "Incorrect first party data error message") + continue + } + + assert.Nil(t, reqExt.GetPrebid().BidderConfigs, "Bidder specific FPD config should be removed from request") + + assert.Nil(t, err, "No error should be returned") + assert.Equal(t, len(fpdFile.BidderConfigFPD), len(fpdData), "Incorrect fpd data") + + for bidderName, bidderFPD := range fpdFile.BidderConfigFPD { + + if bidderFPD.Site != nil { + resSite := fpdData[bidderName].Site + for k, v := range bidderFPD.Site { + assert.NotNil(t, resSite[k], "Property is not found in result site") + assert.JSONEq(t, string(v), string(resSite[k]), "site is incorrect") + } + } else { + assert.Nil(t, fpdData[bidderName].Site, "Result site should be also nil") + } + + if bidderFPD.App != nil { + resApp := fpdData[bidderName].App + for k, v := range bidderFPD.App { + assert.NotNil(t, resApp[k], "Property is not found in result app") + assert.JSONEq(t, string(v), string(resApp[k]), "app is incorrect") + } + } else { + assert.Nil(t, fpdData[bidderName].App, "Result app should be also nil") + } + + if bidderFPD.User != nil { + resUser := fpdData[bidderName].User + for k, v := range bidderFPD.User { + assert.NotNil(t, resUser[k], "Property is not found in result user") + assert.JSONEq(t, string(v), string(resUser[k]), "site is incorrect") + } + } else { + assert.Nil(t, fpdData[bidderName].User, "Result user should be also nil") + } + } + } + } +} + +func TestResolveFPD(t *testing.T) { + + if specFiles, err := ioutil.ReadDir("./tests/resolvefpd"); err == nil { + for _, specFile := range specFiles { + fileName := "./tests/resolvefpd/" + specFile.Name() + + fpdFile, err := loadFpdFile(fileName) + if err != nil { + t.Errorf("Unable to load file: %s", fileName) + } + + var inputReq openrtb2.BidRequest + err = json.Unmarshal(fpdFile.InputRequestData, &inputReq) + if err != nil { + t.Errorf("Unable to unmarshal input request: %s", fileName) + } + + var inputReqCopy openrtb2.BidRequest + err = json.Unmarshal(fpdFile.InputRequestData, &inputReqCopy) + if err != nil { + t.Errorf("Unable to unmarshal input request: %s", fileName) + } + + var outputReq openrtb2.BidRequest + err = json.Unmarshal(fpdFile.OutputRequestData, &outputReq) + if err != nil { + t.Errorf("Unable to unmarshal output request: %s", fileName) + } + + reqExtFPD := make(map[string][]byte, 3) + reqExtFPD["site"] = fpdFile.GlobalFPD["site"] + reqExtFPD["app"] = fpdFile.GlobalFPD["app"] + reqExtFPD["user"] = fpdFile.GlobalFPD["user"] + + reqFPD := make(map[string][]openrtb2.Data, 3) + + reqFPDSiteContentData := fpdFile.GlobalFPD[siteContentDataKey] + if len(reqFPDSiteContentData) > 0 { + var siteConData []openrtb2.Data + err = json.Unmarshal(reqFPDSiteContentData, &siteConData) + if err != nil { + t.Errorf("Unable to unmarshal site.content.data: %s", fileName) + } + reqFPD[siteContentDataKey] = siteConData + } + + reqFPDAppContentData := fpdFile.GlobalFPD[appContentDataKey] + if len(reqFPDAppContentData) > 0 { + var appConData []openrtb2.Data + err = json.Unmarshal(reqFPDAppContentData, &appConData) + if err != nil { + t.Errorf("Unable to unmarshal app.content.data: %s", fileName) + } + reqFPD[appContentDataKey] = appConData + } + + reqFPDUserData := fpdFile.GlobalFPD[userDataKey] + if len(reqFPDUserData) > 0 { + var userData []openrtb2.Data + err = json.Unmarshal(reqFPDUserData, &userData) + if err != nil { + t.Errorf("Unable to unmarshal app.content.data: %s", fileName) + } + reqFPD[userDataKey] = userData + } + if fpdFile.BidderConfigFPD == nil { + fpdFile.BidderConfigFPD = make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2) + fpdFile.BidderConfigFPD["appnexus"] = &openrtb_ext.ORTB2{} + } + + resultFPD, errL := ResolveFPD(&inputReq, fpdFile.BidderConfigFPD, reqExtFPD, reqFPD, []string{"appnexus"}) + + if len(errL) == 0 { + assert.Equal(t, inputReq, inputReqCopy, "Original request should not be modified") + + bidderFPD := resultFPD["appnexus"] + + if outputReq.Site != nil && len(outputReq.Site.Ext) > 0 { + resSiteExt := bidderFPD.Site.Ext + expectedSiteExt := outputReq.Site.Ext + bidderFPD.Site.Ext = nil + outputReq.Site.Ext = nil + assert.JSONEq(t, string(expectedSiteExt), string(resSiteExt), "site.ext is incorrect") + + assert.Equal(t, outputReq.Site, bidderFPD.Site, "Site is incorrect") + } + if outputReq.App != nil && len(outputReq.App.Ext) > 0 { + resAppExt := bidderFPD.App.Ext + expectedAppExt := outputReq.App.Ext + bidderFPD.App.Ext = nil + outputReq.App.Ext = nil + assert.JSONEq(t, string(expectedAppExt), string(resAppExt), "app.ext is incorrect") + + assert.Equal(t, outputReq.App, bidderFPD.App, "App is incorrect") + } + if outputReq.User != nil && len(outputReq.User.Ext) > 0 { + resUserExt := bidderFPD.User.Ext + expectedUserExt := outputReq.User.Ext + bidderFPD.User.Ext = nil + outputReq.User.Ext = nil + assert.JSONEq(t, string(expectedUserExt), string(resUserExt), "user.ext is incorrect") + + assert.Equal(t, outputReq.User, bidderFPD.User, "User is incorrect") + } + } else { + assert.ElementsMatch(t, errL, fpdFile.ValidationErrors, "Incorrect first party data warning message") + } + + } + } +} + +func TestExtractFPDForBidders(t *testing.T) { + + if specFiles, err := ioutil.ReadDir("./tests/extractfpdforbidders"); err == nil { + for _, specFile := range specFiles { + fileName := "./tests/extractfpdforbidders/" + specFile.Name() + fpdFile, err := loadFpdFile(fileName) + + if err != nil { + t.Errorf("Unable to load file: %s", fileName) + } + + var expectedRequest openrtb2.BidRequest + err = json.Unmarshal(fpdFile.OutputRequestData, &expectedRequest) + if err != nil { + t.Errorf("Unable to unmarshal input request: %s", fileName) + } + + resultRequest := &openrtb_ext.RequestWrapper{} + resultRequest.BidRequest = &openrtb2.BidRequest{} + err = json.Unmarshal(fpdFile.InputRequestData, resultRequest.BidRequest) + assert.NoError(t, err, "Error should be nil") + + resultFPD, errL := ExtractFPDForBidders(resultRequest) + + if len(fpdFile.ValidationErrors) > 0 { + assert.Equal(t, len(fpdFile.ValidationErrors), len(errL), "Incorrect number of errors was returned") + assert.ElementsMatch(t, errL, fpdFile.ValidationErrors, "Incorrect errors were returned") + //in case or error no further assertions needed + continue + } + assert.Empty(t, errL, "Error should be empty") + assert.Equal(t, len(resultFPD), len(fpdFile.BiddersFPDResolved)) + + for bidderName, expectedValue := range fpdFile.BiddersFPDResolved { + actualValue := resultFPD[bidderName] + if expectedValue.Site != nil { + if len(expectedValue.Site.Ext) > 0 { + assert.JSONEq(t, string(expectedValue.Site.Ext), string(actualValue.Site.Ext), "Incorrect first party data") + expectedValue.Site.Ext = nil + actualValue.Site.Ext = nil + } + assert.Equal(t, expectedValue.Site, actualValue.Site, "Incorrect first party data") + } + if expectedValue.App != nil { + if len(expectedValue.App.Ext) > 0 { + assert.JSONEq(t, string(expectedValue.App.Ext), string(actualValue.App.Ext), "Incorrect first party data") + expectedValue.App.Ext = nil + actualValue.App.Ext = nil + } + assert.Equal(t, expectedValue.App, actualValue.App, "Incorrect first party data") + } + if expectedValue.User != nil { + if len(expectedValue.User.Ext) > 0 { + assert.JSONEq(t, string(expectedValue.User.Ext), string(actualValue.User.Ext), "Incorrect first party data") + expectedValue.User.Ext = nil + actualValue.User.Ext = nil + } + assert.Equal(t, expectedValue.User, actualValue.User, "Incorrect first party data") + } + } + + if expectedRequest.Site != nil { + if len(expectedRequest.Site.Ext) > 0 { + assert.JSONEq(t, string(expectedRequest.Site.Ext), string(resultRequest.BidRequest.Site.Ext), "Incorrect site in request") + expectedRequest.Site.Ext = nil + resultRequest.BidRequest.Site.Ext = nil + } + assert.Equal(t, expectedRequest.Site, resultRequest.BidRequest.Site, "Incorrect site in request") + } + if expectedRequest.App != nil { + if len(expectedRequest.App.Ext) > 0 { + assert.JSONEq(t, string(expectedRequest.App.Ext), string(resultRequest.BidRequest.App.Ext), "Incorrect app in request") + expectedRequest.App.Ext = nil + resultRequest.BidRequest.App.Ext = nil + } + assert.Equal(t, expectedRequest.App, resultRequest.BidRequest.App, "Incorrect app in request") + } + if expectedRequest.User != nil { + if len(expectedRequest.User.Ext) > 0 { + assert.JSONEq(t, string(expectedRequest.User.Ext), string(resultRequest.BidRequest.User.Ext), "Incorrect user in request") + expectedRequest.User.Ext = nil + resultRequest.BidRequest.User.Ext = nil + } + assert.Equal(t, expectedRequest.User, resultRequest.BidRequest.User, "Incorrect user in request") + } + + } + } +} + +func loadFpdFile(filename string) (fpdFile, error) { + var fileData fpdFile + fileContents, err := ioutil.ReadFile(filename) + if err != nil { + return fileData, err + } + err = json.Unmarshal(fileContents, &fileData) + if err != nil { + return fileData, err + } + + return fileData, nil +} + +type fpdFile struct { + InputRequestData json.RawMessage `json:"inputRequestData,omitempty"` + OutputRequestData json.RawMessage `json:"outputRequestData,omitempty"` + BidderConfigFPD map[openrtb_ext.BidderName]*openrtb_ext.ORTB2 `json:"bidderConfigFPD,omitempty"` + BiddersFPDResolved map[openrtb_ext.BidderName]*ResolvedFirstPartyData `json:"biddersFPDResolved,omitempty"` + GlobalFPD map[string]json.RawMessage `json:"globalFPD,omitempty"` + ValidationErrors []*errortypes.BadInput `json:"validationErrors,omitempty"` +} + +func TestResolveUser(t *testing.T) { + + fpdConfigUser := make(map[string]json.RawMessage, 0) + fpdConfigUser["id"] = []byte(`"fpdConfigUserId"`) + fpdConfigUser[yobKey] = []byte(`1980`) + fpdConfigUser[genderKey] = []byte(`"M"`) + fpdConfigUser[keywordsKey] = []byte(`"fpdConfigUserKeywords"`) + fpdConfigUser["data"] = []byte(`[{"id":"UserDataId1", "name":"UserDataName1"}, {"id":"UserDataId2", "name":"UserDataName2"}]`) + fpdConfigUser["ext"] = []byte(`{"data":{"fpdConfigUserExt": 123}}`) + + bidRequestUser := &openrtb2.User{ + ID: "bidRequestUserId", + Yob: 1990, + Gender: "F", + Keywords: "bidRequestUserKeywords", + } + + globalFPD := make(map[string][]byte, 0) + globalFPD[userKey] = []byte(`{"globalFPDUserData": "globalFPDUserDataValue"}`) + + openRtbGlobalFPD := make(map[string][]openrtb2.Data, 0) + openRtbGlobalFPD[userDataKey] = []openrtb2.Data{ + {ID: "openRtbGlobalFPDUserDataId1", Name: "openRtbGlobalFPDUserDataName1"}, + {ID: "openRtbGlobalFPDUserDataId2", Name: "openRtbGlobalFPDUserDataName2"}, + } + + expectedUser := &openrtb2.User{ + ID: "bidRequestUserId", + Yob: 1980, + Gender: "M", + Keywords: "fpdConfigUserKeywords", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDUserDataId1", Name: "openRtbGlobalFPDUserDataName1"}, + {ID: "openRtbGlobalFPDUserDataId2", Name: "openRtbGlobalFPDUserDataName2"}, + }, + } + + testCases := []struct { + description string + bidRequestUserExt []byte + expectedUserExt string + }{ + { + description: "bid request user.ext is nil", + bidRequestUserExt: nil, + expectedUserExt: `{"data":{ + "data":[ + {"id":"UserDataId1","name":"UserDataName1"}, + {"id":"UserDataId2","name":"UserDataName2"} + ], + "fpdConfigUserExt":123, + "globalFPDUserData":"globalFPDUserDataValue", + "id":"fpdConfigUserId" + } + }`, + }, + { + description: "bid request user.ext is not nil", + bidRequestUserExt: []byte(`{"bidRequestUserExt": 1234}`), + expectedUserExt: `{"data":{ + "data":[ + {"id":"UserDataId1","name":"UserDataName1"}, + {"id":"UserDataId2","name":"UserDataName2"} + ], + "fpdConfigUserExt":123, + "globalFPDUserData":"globalFPDUserDataValue", + "id":"fpdConfigUserId" + }, + "bidRequestUserExt":1234 + }`, + }, + } + + for _, test := range testCases { + bidRequestUser.Ext = test.bidRequestUserExt + + fpdConfigUser := make(map[string]json.RawMessage, 0) + fpdConfigUser["id"] = []byte(`"fpdConfigUserId"`) + fpdConfigUser[yobKey] = []byte(`1980`) + fpdConfigUser[genderKey] = []byte(`"M"`) + fpdConfigUser[keywordsKey] = []byte(`"fpdConfigUserKeywords"`) + fpdConfigUser["data"] = []byte(`[{"id":"UserDataId1", "name":"UserDataName1"}, {"id":"UserDataId2", "name":"UserDataName2"}]`) + fpdConfigUser["ext"] = []byte(`{"data":{"fpdConfigUserExt": 123}}`) + fpdConfig := &openrtb_ext.ORTB2{User: fpdConfigUser} + + resultUser, err := resolveUser(fpdConfig, bidRequestUser, globalFPD, openRtbGlobalFPD, "appnexus") + assert.NoError(t, err, "No error should be returned") + + assert.JSONEq(t, test.expectedUserExt, string(resultUser.Ext), "Result user.Ext is incorrect") + resultUser.Ext = nil + assert.Equal(t, expectedUser, resultUser, "Result user is incorrect") + } + +} + +func TestResolveUserNilValues(t *testing.T) { + resultUser, err := resolveUser(nil, nil, nil, nil, "appnexus") + assert.NoError(t, err, "No error should be returned") + assert.Nil(t, resultUser, "Result user should be nil") +} + +func TestResolveUserBadInput(t *testing.T) { + fpdConfigUser := make(map[string]json.RawMessage, 0) + fpdConfigUser["id"] = []byte(`"fpdConfigUserId"`) + fpdConfig := &openrtb_ext.ORTB2{User: fpdConfigUser} + + resultUser, err := resolveUser(fpdConfig, nil, nil, nil, "appnexus") + assert.Error(t, err, "Error should be returned") + assert.Equal(t, "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config", err.Error(), "Incorrect error message") + assert.Nil(t, resultUser, "Result user should be nil") +} + +func TestMergeUsers(t *testing.T) { + + originalUser := &openrtb2.User{ + ID: "bidRequestUserId", + Yob: 1980, + Gender: "M", + Keywords: "fpdConfigUserKeywords", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDUserDataId1", Name: "openRtbGlobalFPDUserDataName1"}, + {ID: "openRtbGlobalFPDUserDataId2", Name: "openRtbGlobalFPDUserDataName2"}, + }, + Ext: []byte(`{"bidRequestUserExt": 1234}`), + } + fpdConfigUser := make(map[string]json.RawMessage, 0) + fpdConfigUser["id"] = []byte(`"fpdConfigUserId"`) + fpdConfigUser[yobKey] = []byte(`1980`) + fpdConfigUser[genderKey] = []byte(`"M"`) + fpdConfigUser[keywordsKey] = []byte(`"fpdConfigUserKeywords"`) + fpdConfigUser["data"] = []byte(`[{"id":"UserDataId1", "name":"UserDataName1"}, {"id":"UserDataId2", "name":"UserDataName2"}]`) + fpdConfigUser["ext"] = []byte(`{"data":{"fpdConfigUserExt": 123}}`) + + resultUser, err := mergeUsers(originalUser, fpdConfigUser) + assert.NoError(t, err, "No error should be returned") + + expectedUserExt := `{"bidRequestUserExt":1234, + "data":{ + "data":[ + {"id":"UserDataId1","name":"UserDataName1"}, + {"id":"UserDataId2","name":"UserDataName2"}], + "fpdConfigUserExt":123, + "id":"fpdConfigUserId"} + }` + assert.JSONEq(t, expectedUserExt, string(resultUser.Ext), "Result user.Ext is incorrect") + resultUser.Ext = nil + + expectedUser := openrtb2.User{ + ID: "bidRequestUserId", + Yob: 1980, + Gender: "M", + Keywords: "fpdConfigUserKeywords", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDUserDataId1", Name: "openRtbGlobalFPDUserDataName1"}, + {ID: "openRtbGlobalFPDUserDataId2", Name: "openRtbGlobalFPDUserDataName2"}, + }, + } + assert.Equal(t, expectedUser, resultUser, "Result user is incorrect") +} + +func TestResolveExtension(t *testing.T) { + + testCases := []struct { + description string + fpdConfig map[string]json.RawMessage + originalExt json.RawMessage + expectedExt string + }{ + {description: "Fpd config with ext only", + fpdConfig: map[string]json.RawMessage{"ext": json.RawMessage(`{"data":{"fpdConfigUserExt": 123}}`)}, + originalExt: json.RawMessage(`{"bidRequestUserExt": 1234}`), + expectedExt: `{"bidRequestUserExt":1234, "data":{"fpdConfigUserExt":123}}`, + }, + {description: "Fpd config with ext and another property", + fpdConfig: map[string]json.RawMessage{"ext": json.RawMessage(`{"data":{"fpdConfigUserExt": 123}}`), "prebid": json.RawMessage(`{"prebidData":{"isPrebid": true}}`)}, + originalExt: json.RawMessage(`{"bidRequestUserExt": 1234}`), + expectedExt: `{"bidRequestUserExt":1234, "data":{"fpdConfigUserExt":123, "prebid":{"prebidData":{"isPrebid": true}}}}`, + }, + {description: "Fpd config empty", + fpdConfig: nil, + originalExt: json.RawMessage(`{"bidRequestUserExt": 1234}`), + expectedExt: `{"bidRequestUserExt":1234}`, + }, + {description: "Original ext empty", + fpdConfig: map[string]json.RawMessage{"ext": json.RawMessage(`{"data":{"fpdConfigUserExt": 123}}`)}, + originalExt: nil, + expectedExt: `{"data":{"ext":{"data":{"fpdConfigUserExt":123}}}}`, + }, + } + + for _, test := range testCases { + resExt, err := resolveExtension(test.fpdConfig, test.originalExt) + assert.NoError(t, err, "No error should be returned") + assert.JSONEq(t, test.expectedExt, string(resExt), "result ext is incorrect") + } +} + +func TestResolveSite(t *testing.T) { + + fpdConfigSite := make(map[string]json.RawMessage, 0) + fpdConfigSite["id"] = []byte(`"fpdConfigSiteId"`) + fpdConfigSite[keywordsKey] = []byte(`"fpdConfigSiteKeywords"`) + fpdConfigSite[nameKey] = []byte(`"fpdConfigSiteName"`) + fpdConfigSite[pageKey] = []byte(`"fpdConfigSitePage"`) + fpdConfigSite["data"] = []byte(`[{"id":"SiteDataId1", "name":"SiteDataName1"}, {"id":"SiteDataId2", "name":"SiteDataName2"}]`) + fpdConfigSite["ext"] = []byte(`{"data":{"fpdConfigSiteExt": 123}}`) + + bidRequestSite := &openrtb2.Site{ + ID: "bidRequestSiteId", + Keywords: "bidRequestSiteKeywords", + Name: "bidRequestSiteName", + Page: "bidRequestSitePage", + Content: &openrtb2.Content{ + ID: "bidRequestSiteContentId", + Episode: 4, + Data: []openrtb2.Data{ + {ID: "bidRequestSiteContentDataId1", Name: "bidRequestSiteContentDataName1"}, + {ID: "bidRequestSiteContentDataId2", Name: "bidRequestSiteContentDataName2"}, + }, + }, + } + + globalFPD := make(map[string][]byte, 0) + globalFPD[siteKey] = []byte(`{"globalFPDSiteData": "globalFPDSiteDataValue"}`) + + openRtbGlobalFPD := make(map[string][]openrtb2.Data, 0) + openRtbGlobalFPD[siteContentDataKey] = []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteContentDataId1", Name: "openRtbGlobalFPDSiteContentDataName1"}, + {ID: "openRtbGlobalFPDSiteContentDataId2", Name: "openRtbGlobalFPDSiteContentDataName2"}, + } + + expectedSite := &openrtb2.Site{ + ID: "bidRequestSiteId", + Keywords: "fpdConfigSiteKeywords", + Name: "bidRequestSiteName", + Page: "bidRequestSitePage", + Content: &openrtb2.Content{ + ID: "bidRequestSiteContentId", + Episode: 4, + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteContentDataId1", Name: "openRtbGlobalFPDSiteContentDataName1"}, + {ID: "openRtbGlobalFPDSiteContentDataId2", Name: "openRtbGlobalFPDSiteContentDataName2"}, + }, + }, + } + + testCases := []struct { + description string + bidRequestSiteExt []byte + expectedSiteExt string + siteContentNil bool + }{ + { + description: "bid request site.ext is nil", + bidRequestSiteExt: nil, + expectedSiteExt: `{"data":{ + "data":[ + {"id":"SiteDataId1","name":"SiteDataName1"}, + {"id":"SiteDataId2","name":"SiteDataName2"} + ], + "fpdConfigSiteExt":123, + "globalFPDSiteData":"globalFPDSiteDataValue", + "id":"fpdConfigSiteId" + } + }`, + siteContentNil: false, + }, + { + description: "bid request site.ext is not nil", + bidRequestSiteExt: []byte(`{"bidRequestSiteExt": 1234}`), + expectedSiteExt: `{"data":{ + "data":[ + {"id":"SiteDataId1","name":"SiteDataName1"}, + {"id":"SiteDataId2","name":"SiteDataName2"} + ], + "fpdConfigSiteExt":123, + "globalFPDSiteData":"globalFPDSiteDataValue", + "id":"fpdConfigSiteId" + }, + "bidRequestSiteExt":1234 + }`, + siteContentNil: false, + }, + { + description: "bid request site.content.data is nil ", + bidRequestSiteExt: []byte(`{"bidRequestSiteExt": 1234}`), + expectedSiteExt: `{"data":{ + "data":[ + {"id":"SiteDataId1","name":"SiteDataName1"}, + {"id":"SiteDataId2","name":"SiteDataName2"} + ], + "fpdConfigSiteExt":123, + "globalFPDSiteData":"globalFPDSiteDataValue", + "id":"fpdConfigSiteId" + }, + "bidRequestSiteExt":1234 + }`, + siteContentNil: true, + }, + } + + for _, test := range testCases { + if test.siteContentNil { + bidRequestSite.Content = nil + expectedSite.Content = &openrtb2.Content{Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteContentDataId1", Name: "openRtbGlobalFPDSiteContentDataName1"}, + {ID: "openRtbGlobalFPDSiteContentDataId2", Name: "openRtbGlobalFPDSiteContentDataName2"}, + }} + } + + bidRequestSite.Ext = test.bidRequestSiteExt + + fpdConfigSite := make(map[string]json.RawMessage, 0) + fpdConfigSite["id"] = []byte(`"fpdConfigSiteId"`) + fpdConfigSite[keywordsKey] = []byte(`"fpdConfigSiteKeywords"`) + fpdConfigSite["data"] = []byte(`[{"id":"SiteDataId1", "name":"SiteDataName1"}, {"id":"SiteDataId2", "name":"SiteDataName2"}]`) + fpdConfigSite["ext"] = []byte(`{"data":{"fpdConfigSiteExt": 123}}`) + fpdConfig := &openrtb_ext.ORTB2{Site: fpdConfigSite} + + resultSite, err := resolveSite(fpdConfig, bidRequestSite, globalFPD, openRtbGlobalFPD, "appnexus") + assert.NoError(t, err, "No error should be returned") + + assert.JSONEq(t, test.expectedSiteExt, string(resultSite.Ext), "Result site.Ext is incorrect") + resultSite.Ext = nil + assert.Equal(t, expectedSite, resultSite, "Result site is incorrect") + } + +} + +func TestResolveSiteNilValues(t *testing.T) { + resultSite, err := resolveSite(nil, nil, nil, nil, "appnexus") + assert.NoError(t, err, "No error should be returned") + assert.Nil(t, resultSite, "Result site should be nil") +} + +func TestResolveSiteBadInput(t *testing.T) { + fpdConfigSite := make(map[string]json.RawMessage, 0) + fpdConfigSite["id"] = []byte(`"fpdConfigSiteId"`) + fpdConfig := &openrtb_ext.ORTB2{Site: fpdConfigSite} + + resultSite, err := resolveSite(fpdConfig, nil, nil, nil, "appnexus") + assert.Error(t, err, "Error should be returned") + assert.Equal(t, "incorrect First Party Data for bidder appnexus: Site object is not defined in request, but defined in FPD config", err.Error(), "Incorrect error message") + assert.Nil(t, resultSite, "Result site should be nil") +} + +func TestMergeSites(t *testing.T) { + + originalSite := &openrtb2.Site{ + ID: "bidRequestSiteId", + Keywords: "bidRequestSiteKeywords", + Page: "bidRequestSitePage", + Name: "bidRequestSiteName", + Domain: "bidRequestSiteDomain", + Cat: []string{"books1", "magazines1"}, + SectionCat: []string{"books2", "magazines2"}, + PageCat: []string{"books3", "magazines3"}, + Search: "bidRequestSiteSearch", + Ref: "bidRequestSiteRef", + Content: &openrtb2.Content{ + Title: "bidRequestSiteContentTitle", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteDataId1", Name: "openRtbGlobalFPDSiteDataName1"}, + {ID: "openRtbGlobalFPDSiteDataId2", Name: "openRtbGlobalFPDSiteDataName2"}, + }, + }, + Ext: []byte(`{"bidRequestSiteExt": 1234}`), + } + fpdConfigSite := make(map[string]json.RawMessage, 0) + fpdConfigSite["id"] = []byte(`"fpdConfigSiteId"`) + fpdConfigSite[keywordsKey] = []byte(`"fpdConfigSiteKeywords"`) + fpdConfigSite[pageKey] = []byte(`"fpdConfigSitePage"`) + fpdConfigSite[nameKey] = []byte(`"fpdConfigSiteName"`) + fpdConfigSite[domainKey] = []byte(`"fpdConfigSiteDomain"`) + fpdConfigSite[catKey] = []byte(`["cars1", "auto1"]`) + fpdConfigSite[sectionCatKey] = []byte(`["cars2", "auto2"]`) + fpdConfigSite[pageCatKey] = []byte(`["cars3", "auto3"]`) + fpdConfigSite[searchKey] = []byte(`"fpdConfigSiteSearch"`) + fpdConfigSite[refKey] = []byte(`"fpdConfigSiteRef"`) + fpdConfigSite["data"] = []byte(`[{"id":"SiteDataId1", "name":"SiteDataName1"}, {"id":"SiteDataId2", "name":"SiteDataName2"}]`) + fpdConfigSite["ext"] = []byte(`{"data":{"fpdConfigSiteExt": 123}}`) + + resultSite, err := mergeSites(originalSite, fpdConfigSite, "appnexus") + assert.NoError(t, err, "No error should be returned") + + expectedSiteExt := `{"bidRequestSiteExt":1234, + "data":{ + "data":[ + {"id":"SiteDataId1","name":"SiteDataName1"}, + {"id":"SiteDataId2","name":"SiteDataName2"}], + "fpdConfigSiteExt":123, + "id":"fpdConfigSiteId"} + }` + assert.JSONEq(t, expectedSiteExt, string(resultSite.Ext), "Result user.Ext is incorrect") + resultSite.Ext = nil + + expectedSite := openrtb2.Site{ + ID: "bidRequestSiteId", + Keywords: "fpdConfigSiteKeywords", + Page: "fpdConfigSitePage", + Name: "fpdConfigSiteName", + Domain: "fpdConfigSiteDomain", + Cat: []string{"cars1", "auto1"}, + SectionCat: []string{"cars2", "auto2"}, + PageCat: []string{"cars3", "auto3"}, + Search: "fpdConfigSiteSearch", + Ref: "fpdConfigSiteRef", + Content: &openrtb2.Content{ + Title: "bidRequestSiteContentTitle", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDSiteDataId1", Name: "openRtbGlobalFPDSiteDataName1"}, + {ID: "openRtbGlobalFPDSiteDataId2", Name: "openRtbGlobalFPDSiteDataName2"}, + }, + }, + Ext: nil, + } + assert.Equal(t, expectedSite, resultSite, "Result user is incorrect") +} + +func TestResolveApp(t *testing.T) { + + fpdConfigApp := make(map[string]json.RawMessage, 0) + fpdConfigApp["id"] = []byte(`"fpdConfigAppId"`) + fpdConfigApp[keywordsKey] = []byte(`"fpdConfigAppKeywords"`) + fpdConfigApp[nameKey] = []byte(`"fpdConfigAppName"`) + fpdConfigApp[bundleKey] = []byte(`"fpdConfigAppBundle"`) + fpdConfigApp["data"] = []byte(`[{"id":"AppDataId1", "name":"AppDataName1"}, {"id":"AppDataId2", "name":"AppDataName2"}]`) + fpdConfigApp["ext"] = []byte(`{"data":{"fpdConfigAppExt": 123}}`) + + bidRequestApp := &openrtb2.App{ + ID: "bidRequestAppId", + Keywords: "bidRequestAppKeywords", + Name: "bidRequestAppName", + Bundle: "bidRequestAppBundle", + Content: &openrtb2.Content{ + ID: "bidRequestAppContentId", + Episode: 4, + Data: []openrtb2.Data{ + {ID: "bidRequestAppContentDataId1", Name: "bidRequestAppContentDataName1"}, + {ID: "bidRequestAppContentDataId2", Name: "bidRequestAppContentDataName2"}, + }, + }, + } + + globalFPD := make(map[string][]byte, 0) + globalFPD[appKey] = []byte(`{"globalFPDAppData": "globalFPDAppDataValue"}`) + + openRtbGlobalFPD := make(map[string][]openrtb2.Data, 0) + openRtbGlobalFPD[appContentDataKey] = []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppContentDataId1", Name: "openRtbGlobalFPDAppContentDataName1"}, + {ID: "openRtbGlobalFPDAppContentDataId2", Name: "openRtbGlobalFPDAppContentDataName2"}, + } + + expectedApp := &openrtb2.App{ + ID: "bidRequestAppId", + Keywords: "fpdConfigAppKeywords", + Name: "bidRequestAppName", + Bundle: "bidRequestAppBundle", + Content: &openrtb2.Content{ + ID: "bidRequestAppContentId", + Episode: 4, + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppContentDataId1", Name: "openRtbGlobalFPDAppContentDataName1"}, + {ID: "openRtbGlobalFPDAppContentDataId2", Name: "openRtbGlobalFPDAppContentDataName2"}, + }, + }, + } + + testCases := []struct { + description string + bidRequestAppExt []byte + expectedAppExt string + appContentNil bool + }{ + { + description: "bid request app.ext is nil", + bidRequestAppExt: nil, + expectedAppExt: `{"data":{ + "data":[ + {"id":"AppDataId1","name":"AppDataName1"}, + {"id":"AppDataId2","name":"AppDataName2"} + ], + "fpdConfigAppExt":123, + "globalFPDAppData":"globalFPDAppDataValue", + "id":"fpdConfigAppId" + } + }`, + appContentNil: false, + }, + { + description: "bid request app.ext is not nil", + bidRequestAppExt: []byte(`{"bidRequestAppExt": 1234}`), + expectedAppExt: `{"data":{ + "data":[ + {"id":"AppDataId1","name":"AppDataName1"}, + {"id":"AppDataId2","name":"AppDataName2"} + ], + "fpdConfigAppExt":123, + "globalFPDAppData":"globalFPDAppDataValue", + "id":"fpdConfigAppId" + }, + "bidRequestAppExt":1234 + }`, + appContentNil: false, + }, + { + description: "bid request app.content.data is nil ", + bidRequestAppExt: []byte(`{"bidRequestAppExt": 1234}`), + expectedAppExt: `{"data":{ + "data":[ + {"id":"AppDataId1","name":"AppDataName1"}, + {"id":"AppDataId2","name":"AppDataName2"} + ], + "fpdConfigAppExt":123, + "globalFPDAppData":"globalFPDAppDataValue", + "id":"fpdConfigAppId" + }, + "bidRequestAppExt":1234 + }`, + appContentNil: true, + }, + } + + for _, test := range testCases { + if test.appContentNil { + bidRequestApp.Content = nil + expectedApp.Content = &openrtb2.Content{Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppContentDataId1", Name: "openRtbGlobalFPDAppContentDataName1"}, + {ID: "openRtbGlobalFPDAppContentDataId2", Name: "openRtbGlobalFPDAppContentDataName2"}, + }} + } + + bidRequestApp.Ext = test.bidRequestAppExt + + fpdConfigApp := make(map[string]json.RawMessage, 0) + fpdConfigApp["id"] = []byte(`"fpdConfigAppId"`) + fpdConfigApp[keywordsKey] = []byte(`"fpdConfigAppKeywords"`) + fpdConfigApp["data"] = []byte(`[{"id":"AppDataId1", "name":"AppDataName1"}, {"id":"AppDataId2", "name":"AppDataName2"}]`) + fpdConfigApp["ext"] = []byte(`{"data":{"fpdConfigAppExt": 123}}`) + fpdConfig := &openrtb_ext.ORTB2{App: fpdConfigApp} + + resultApp, err := resolveApp(fpdConfig, bidRequestApp, globalFPD, openRtbGlobalFPD, "appnexus") + assert.NoError(t, err, "No error should be returned") + + assert.JSONEq(t, test.expectedAppExt, string(resultApp.Ext), "Result app.Ext is incorrect") + resultApp.Ext = nil + assert.Equal(t, expectedApp, resultApp, "Result app is incorrect") + } + +} + +func TestResolveAppNilValues(t *testing.T) { + resultApp, err := resolveApp(nil, nil, nil, nil, "appnexus") + assert.NoError(t, err, "No error should be returned") + assert.Nil(t, resultApp, "Result app should be nil") +} + +func TestResolveAppBadInput(t *testing.T) { + fpdConfigApp := make(map[string]json.RawMessage, 0) + fpdConfigApp["id"] = []byte(`"fpdConfigAppId"`) + fpdConfig := &openrtb_ext.ORTB2{App: fpdConfigApp} + + resultApp, err := resolveApp(fpdConfig, nil, nil, nil, "appnexus") + assert.Error(t, err, "Error should be returned") + assert.Equal(t, "incorrect First Party Data for bidder appnexus: App object is not defined in request, but defined in FPD config", err.Error(), "Incorrect error message") + assert.Nil(t, resultApp, "Result app should be nil") +} + +func TestMergeApps(t *testing.T) { + + originalApp := &openrtb2.App{ + ID: "bidRequestAppId", + Keywords: "bidRequestAppKeywords", + Name: "bidRequestAppName", + Domain: "bidRequestAppDomain", + Bundle: "bidRequestAppBundle", + StoreURL: "bidRequestAppStoreUrl", + Ver: "bidRequestAppVer", + Cat: []string{"books1", "magazines1"}, + SectionCat: []string{"books2", "magazines2"}, + PageCat: []string{"books3", "magazines3"}, + Content: &openrtb2.Content{ + Title: "bidRequestAppContentTitle", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppDataId1", Name: "openRtbGlobalFPDAppDataName1"}, + {ID: "openRtbGlobalFPDAppDataId2", Name: "openRtbGlobalFPDAppDataName2"}, + }, + }, + Ext: []byte(`{"bidRequestAppExt": 1234}`), + } + fpdConfigApp := make(map[string]json.RawMessage, 0) + fpdConfigApp["id"] = []byte(`"fpdConfigAppId"`) + fpdConfigApp[keywordsKey] = []byte(`"fpdConfigAppKeywords"`) + fpdConfigApp[nameKey] = []byte(`"fpdConfigAppName"`) + fpdConfigApp[domainKey] = []byte(`"fpdConfigAppDomain"`) + fpdConfigApp[bundleKey] = []byte(`"fpdConfigAppBundle"`) + fpdConfigApp[storeUrlKey] = []byte(`"fpdConfigAppStoreUrl"`) + fpdConfigApp[verKey] = []byte(`"fpdConfigAppVer"`) + fpdConfigApp[catKey] = []byte(`["cars1", "auto1"]`) + fpdConfigApp[sectionCatKey] = []byte(`["cars2", "auto2"]`) + fpdConfigApp[pageCatKey] = []byte(`["cars3", "auto3"]`) + fpdConfigApp["data"] = []byte(`[{"id":"AppDataId1", "name":"AppDataName1"}, {"id":"AppDataId2", "name":"AppDataName2"}]`) + fpdConfigApp["ext"] = []byte(`{"data":{"fpdConfigAppExt": 123}}`) + + resultApp, err := mergeApps(originalApp, fpdConfigApp) + assert.NoError(t, err, "No error should be returned") + + expectedAppExt := `{"bidRequestAppExt":1234, + "data":{ + "data":[ + {"id":"AppDataId1","name":"AppDataName1"}, + {"id":"AppDataId2","name":"AppDataName2"}], + "fpdConfigAppExt":123, + "id":"fpdConfigAppId"} + }` + assert.JSONEq(t, expectedAppExt, string(resultApp.Ext), "Result user.Ext is incorrect") + resultApp.Ext = nil + + expectedApp := openrtb2.App{ + ID: "bidRequestAppId", + Keywords: "fpdConfigAppKeywords", + Name: "fpdConfigAppName", + Domain: "fpdConfigAppDomain", + Bundle: "fpdConfigAppBundle", + Ver: "fpdConfigAppVer", + StoreURL: "fpdConfigAppStoreUrl", + Cat: []string{"cars1", "auto1"}, + SectionCat: []string{"cars2", "auto2"}, + PageCat: []string{"cars3", "auto3"}, + Content: &openrtb2.Content{ + Title: "bidRequestAppContentTitle", + Data: []openrtb2.Data{ + {ID: "openRtbGlobalFPDAppDataId1", Name: "openRtbGlobalFPDAppDataName1"}, + {ID: "openRtbGlobalFPDAppDataId2", Name: "openRtbGlobalFPDAppDataName2"}, + }, + }, + Ext: nil, + } + assert.Equal(t, expectedApp, resultApp, "Result user is incorrect") +} + +func TestBuildExtData(t *testing.T) { + testCases := []struct { + description string + input []byte + expectedRes string + }{ + { + description: "Input object with int value", + input: []byte(`{"someData": 123}`), + expectedRes: `{"data": {"someData": 123}}`, + }, + { + description: "Input object with bool value", + input: []byte(`{"someData": true}`), + expectedRes: `{"data": {"someData": true}}`, + }, + { + description: "Input object with string value", + input: []byte(`{"someData": "true"}`), + expectedRes: `{"data": {"someData": "true"}}`, + }, + { + description: "No input object", + input: []byte(`{}`), + expectedRes: `{"data": {}}`, + }, + { + description: "Input object with object value", + input: []byte(`{"someData": {"moreFpdData": "fpddata"}}`), + expectedRes: `{"data": {"someData": {"moreFpdData": "fpddata"}}}`, + }, + } + + for _, test := range testCases { + actualRes := buildExtData(test.input) + assert.JSONEq(t, test.expectedRes, string(actualRes), "Incorrect result data") + } +} diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-null.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-null.json new file mode 100644 index 00000000000..6d2d54f6508 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-null.json @@ -0,0 +1,20 @@ +{ + "description": "Bidder config for bidder is null - expected bidder fpd with no data", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": null + } + ] + }, + "outputRequestData": { + }, + "bidderConfigFPD": { + "appnexus": {} + } +} + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-ortb-null.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-ortb-null.json new file mode 100644 index 00000000000..3bafae6dc87 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-config-ortb-null.json @@ -0,0 +1,22 @@ +{ + "description": "Bidder config.ortb2 for bidder is null - expected bidder fpd with no data", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": null + } + } + ] + }, + "outputRequestData": { + }, + "bidderConfigFPD": { + "appnexus": { + } + } +} diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated.json new file mode 100644 index 00000000000..356b84aac35 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-duplicated.json @@ -0,0 +1,63 @@ +{ + "description": "Verifies error presence in case more than one bidder config specified for the same bidder", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "sitefpddata": "sitefpddata", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata" + } + } + } + } + } + }, + { + "bidders": [ + "appnexus", + "telaria", + "testBidder2" + ], + "config": { + "ortb2": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + } + } + } + ] + }, + "outputRequestData": {}, + "bidderConfigFPD": {}, + "validationErrors": [ + {"Message": "multiple First Party Data bidder configs provided for bidder: appnexus"} + ] +} + + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-empty.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-empty.json new file mode 100644 index 00000000000..d127e5df15f --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-empty.json @@ -0,0 +1,11 @@ +{ + "description": "Bidder config is empty - no bidder fpd expected for any bidders", + "inputRequestData": { + "data": {}, + "bidderconfig": [] + }, + "outputRequestData": {}, + "bidderConfigFPD": {} +} + + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-multiple-bidder.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-multiple-bidder.json new file mode 100644 index 00000000000..48ea27a7c42 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-multiple-bidder.json @@ -0,0 +1,109 @@ +{ + "description": "Extracts bidder configs for multiple bidders, one config has two bidders; expect to have bidder FPD configs for all bidders", + "inputRequestData": { + "data": {}, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "sitefpddata": "sitefpddata", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata" + } + } + } + } + } + }, + { + "bidders": [ + "telaria", + "testBidder2" + ], + "config": { + "ortb2": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + } + } + } + ] + }, + "outputRequestData": { + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "sitefpddata": "sitefpddata", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata" + } + } + } + }, + "telaria": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + }, + "testBidder2": { + "user": { + "id": "telariaUserData", + "ext": { + "data": { + "userdata": "fpduserdata" + } + } + }, + "app": { + "id": "telariaAppData", + "ext": { + "data": { + "appdata": "fpdappdata" + } + } + } + } + } +} diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-not-specified.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-not-specified.json new file mode 100644 index 00000000000..d3dfc5b0943 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-not-specified.json @@ -0,0 +1,7 @@ +{ + "description": "Bidder config not specified", + "inputRequestData": {}, + "outputRequestData": {}, + "bidderConfigFPD": {} +} + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-config-null.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-null.json new file mode 100644 index 00000000000..1063f7ecf40 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-config-null.json @@ -0,0 +1,11 @@ +{ + "description": "Bidder config is null - no bidder fpd expected", + "inputRequestData": { + "data": {}, + "bidderconfig": null + }, + "outputRequestData": {}, + "bidderConfigFPD": {} +} + + diff --git a/firstpartydata/tests/extractbidderconfigfpd/bidder-list-specified-only.json b/firstpartydata/tests/extractbidderconfigfpd/bidder-list-specified-only.json new file mode 100644 index 00000000000..6c1938bc7c0 --- /dev/null +++ b/firstpartydata/tests/extractbidderconfigfpd/bidder-list-specified-only.json @@ -0,0 +1,15 @@ +{ + "description": "Only global bidder list specified - no bidder fpds expected", + "inputRequestData": { + "data": { + "bidders": [ + "appnexus", + "telaria", + "testBidder" + ] + } + }, + "outputRequestData": {}, + "bidderConfigFPD": {} +} + diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json new file mode 100644 index 00000000000..797846ee7a6 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app-content-data.json @@ -0,0 +1,87 @@ +{ + "description": "req.app.content.data defined; req.ext.prebid.data.bidders contains appnexus; expect req.app.content to be in the resolved FPD for appnexus but extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app.json new file mode 100644 index 00000000000..4f312b3dc01 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-app.json @@ -0,0 +1,80 @@ +{ + "description": "req.app and req.ext.prebid.data.bidders contains appnexus; expect bidder config with app data to be included in the resolved FPD for appnexus", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "publisher": { + "id": "1" + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "app": { + "id": "apnAppId", + "name": "fpdAppName", + "bundle": "fpdAppBundle", + "domain": "fpdAppDomain", + "storeurl": "fpdAppstoreUrl", + "cat": ["books"], + "sectioncat": ["books"], + "pagecat": ["magazines"], + "keywords": "apnKeywords", + "ver": "1.2" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "publisher": { + "id": "1" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "app": { + "id": "reqAppId", + "name": "fpdAppName", + "bundle": "fpdAppBundle", + "domain": "fpdAppDomain", + "storeurl": "fpdAppstoreUrl", + "cat": ["books"], + "sectioncat": ["books"], + "pagecat": ["magazines"], + "keywords": "apnKeywords", + "ver": "1.2", + "publisher": { + "id": "1" + }, + "ext": { + "data": { + "id": "apnAppId" + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-data-user.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-data-user.json new file mode 100644 index 00000000000..368be1e591a --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-data-user.json @@ -0,0 +1,63 @@ +{ + "description": "req.user.data defined; req.ext.prebid.data.bidders contains appnexus; expect req.user.data to be included in the appnexus resolved FPD but extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M" + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site-content-data.json new file mode 100644 index 00000000000..ea20afb5cd0 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site-content-data.json @@ -0,0 +1,96 @@ +{ + "description": "req.site.content.data defined; req.ext.prebid.data.bidders contains appnexus; expect req.site.content.data to be included in the appnexus resolved FPD but extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site.json new file mode 100644 index 00000000000..5fc7c0d7cda --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-defined-site.json @@ -0,0 +1,74 @@ +{ + "description": "req.site defined; req.ext.prebid.data.bidders contains appnexus and contains site configuration; expect bidder config with site data to be included in the resolved FPD for appnexus and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId", + "keywords": "apnKeywords", + "sectioncat": ["books"], + "search": "books", + "pagecat": ["magazines"], + "page": "http://www.foobar.com/testurl.html" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "keywords": "apnKeywords", + "sectioncat": ["books"], + "pagecat": ["magazines"], + "search": "books", + "page": "http://www.foobar.com/testurl.html", + "publisher": { + "id": "1" + }, + "ext": { + "data": { + "id": "apnSiteId" + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app-content-data.json new file mode 100644 index 00000000000..57d79210376 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app-content-data.json @@ -0,0 +1,58 @@ +{ + "description": "req.app.content.data defined; req.ext.prebid.data.bidders is empty; expect req.app.content.data to not be included in any of the resolved bidder FPD and extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app.json new file mode 100644 index 00000000000..e37edf1214f --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-app.json @@ -0,0 +1,43 @@ +{ + "description": "req.app.ext.data is present but req.ext.prebid.data.bidders is empty; expect req.app.ext.data to not be included in any of the resolved bidder FPD and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "keywords": "appKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "someappfpd": "appfpdDataTest" + } + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "keywords": "appKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1 + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site-content-data.json new file mode 100644 index 00000000000..5ff8fb6bebf --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site-content-data.json @@ -0,0 +1,64 @@ +{ + "description": "req.site.content.data defined; req.ext.prebid.data.bidders is empty; expect req.site.content.data not to be included in the resolved FPD for any bidders and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site.json new file mode 100644 index 00000000000..985edd34254 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-site.json @@ -0,0 +1,43 @@ +{ + "description": "req.site.ext.data is present but req.ext.prebid.data.bidders is empty; expect req.site.ext.data to not be included in any of the resolved bidder FPD and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "somesitefpd": "sitefpdDataTest" + } + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1 + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user-data.json new file mode 100644 index 00000000000..f7bc1d818b9 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user-data.json @@ -0,0 +1,42 @@ +{ + "description": "req.user.data defined; req.ext.prebid.data.bidders is empty; expect req.site.content.data do not contain req.app.content.data from the original request", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M" + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user.json new file mode 100644 index 00000000000..cd40d84ddea --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-empty-user.json @@ -0,0 +1,43 @@ +{ + "description": "req.user.ext.data is present but req.ext.prebid.data.bidders is empty; expect req.user.ext.data to not be included in any of the resolved bidders FPD and to have been extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserId", + "keywords": "userKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "someuserfpd": "userfpdDataTest" + } + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [] + } + } + } + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserId", + "keywords": "userKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1 + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app-content-data.json new file mode 100644 index 00000000000..2f753ad5b24 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app-content-data.json @@ -0,0 +1,61 @@ +{ + "description": "req.app.content.data is present but req.ext.prebid is not defined; expect req.app.content.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "domain": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app.json new file mode 100644 index 00000000000..c60c9a86004 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-app.json @@ -0,0 +1,39 @@ +{ + "description": "req.app.ext.data is present but req.ext.prebid is not defined; expect req.app.ext.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "keywords": "appKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "someappfpd": "appfpdDataTest" + } + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "keywords": "appKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp":1, + "data": { + "someappfpd": "appfpdDataTest" + } + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site-content-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site-content-data.json new file mode 100644 index 00000000000..594c8e772f5 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site-content-data.json @@ -0,0 +1,67 @@ +{ + "description": "req.site.content.data is present but req.ext.prebid is not defined; expect req.site.content.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site.json new file mode 100644 index 00000000000..1eb04ff20eb --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-site.json @@ -0,0 +1,39 @@ +{ + "description": "req.site.ext.data is present but req.ext.prebid is not defined; expect req.site.ext.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "somesitefpd": "sitefpdDataTest" + } + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "amp":1, + "data": { + "somesitefpd": "sitefpdDataTest" + } + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user-data.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user-data.json new file mode 100644 index 00000000000..4bb013b1108 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user-data.json @@ -0,0 +1,45 @@ +{ + "description": "req.user.data is present but req.ext.prebid is not defined; expect req.user.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserID", + "keywords": "userKeyword", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userDataId1", + "name": "userDataName1" + }, + { + "id": "userDataId2", + "name": "userDataName2" + } + ] + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user.json b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user.json new file mode 100644 index 00000000000..91ebcab4d93 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/global-fpd-not-defined-user.json @@ -0,0 +1,39 @@ +{ + "description": "req.user.ext.data is present but req.ext.prebid is not defined; expect req.user.ext.data remains the same and no FPD data for bidders", + "inputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserId", + "keywords": "userKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp": 1, + "data": { + "someuserfpd": "userfpdDataTest" + } + } + }, + "test": 1 + }, + "outputRequestData": { + "id": "bid_id", + "user": { + "id": "reqUserId", + "keywords": "userKeywords", + "publisher": { + "id": "1" + }, + "ext": { + "amp":1, + "data": { + "someuserfpd": "userfpdDataTest" + } + } + }, + "test": 1 + }, + "biddersFPDResolved": {}, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-fpd.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-fpd.json new file mode 100644 index 00000000000..62b2a44eead --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-correct-fpd.json @@ -0,0 +1,98 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders has two bidders; req.ext.prebid.bidderconfig is defined for two bidders from req.ext.prebid.data.bidders; expect to see both bidders in resolved bidder FPD", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "user": { + "id": "reqUserId" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus", + "telaria" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + }, + "user": { + "id": "apnUserId" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "keywords": "telariaUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "data": { + "id": "apnSiteId" + } + } + }, + "user": { + "id": "reqUserId", + "ext": { + "data": { + "id": "apnUserId" + } + } + } + }, + "telaria": { + "user": { + "id": "reqUserId", + "keywords": "telariaUserKeywords" + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-user.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-user.json new file mode 100644 index 00000000000..6fed14e9059 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-app-user.json @@ -0,0 +1,184 @@ +{ + "description": "req.app.content.data defined; bidder config defined for appnexus and telaria but only appnexus listed in req.ext.prebid.data.bidders; expect all FPD data to be included only in appnexus resolved FPD and extracted from the original request", + "inputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "user": { + "id": "reqUserId", + "keywords": "reqUserKeyword" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "app": { + "id": "apnFpdAppId", + "name": "apnFpdAppIdAppName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "apnFpdAppIdContentTitle", + "season": "apnFpdAppIdContentSeason", + "data": [ + { + "id": "apnFpdAppIdContentDataId1", + "name": "apnFpdAppIdContentDataName1" + }, + { + "id": "apnFpdAppIdContentDataId2", + "name": "apnFpdAppIdContentDataName2" + } + ] + } + }, + "user": { + "id": "apnUserId", + "keywords": "apnFpdUserKeyword" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "keywords": "telariaFpdUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "name": "reqAppName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "app": { + "id": "reqAppId", + "name": "apnFpdAppIdAppName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + }, + "ext": { + "data": { + "content": { + "len": 900, + "title": "apnFpdAppIdContentTitle", + "season": "apnFpdAppIdContentSeason", + "data": [ + { + "id": "apnFpdAppIdContentDataId1", + "name": "apnFpdAppIdContentDataName1" + }, + { + "id": "apnFpdAppIdContentDataId2", + "name": "apnFpdAppIdContentDataName2" + } + ] + }, + "id": "apnFpdAppId", + "publisher": { + "id": "1" + } + } + } + }, + "user": { + "id": "reqUserId", + "keywords": "apnFpdUserKeyword", + "ext": { + "data": { + "id":"apnUserId" + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-user.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-user.json new file mode 100644 index 00000000000..129087444a3 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-site-user.json @@ -0,0 +1,186 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders has appnexus bidder only; req.ext.prebid.bidderconfig is defined for two bidders appnexus and telaria; expect to see only appnexus in resolved bidder FPD and have extra data in site.ext.data", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "user": { + "id": "reqUserId", + "keywords": "reqUserKeyword" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnFpdSiteId", + "page": "http://www.foobar.com/4321.html", + "name": "apnFpdSiteIdSiteName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "apnFpdSiteIdContentTitle", + "season": "apnFpdSiteIdContentSeason", + "data": [ + { + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" + }, + { + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" + } + ] + } + }, + "user": { + "id": "apnUserId", + "keywords": "apnFpdUserKeyword" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "keywords": "telariaFpdUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/4321.html", + "name": "apnFpdSiteIdSiteName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + }, + "ext": { + "data": { + "content": { + "len": 900, + "title": "apnFpdSiteIdContentTitle", + "season": "apnFpdSiteIdContentSeason", + "data": [ + { + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" + }, + { + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" + } + ] + }, + "id": "apnFpdSiteId", + "publisher": { + "id": "1" + } + } + } + }, + "user": { + "id": "reqUserId", + "keywords": "apnFpdUserKeyword", + "ext": { + "data": { + "id":"apnUserId" + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-user.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-user.json new file mode 100644 index 00000000000..c04838c756c --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-global-and-non-global-user.json @@ -0,0 +1,222 @@ +{ + "description": "req.site and req.user is present; req.ext.prebid.data.bidders has appnexus bidder only; req.ext.prebid.bidderconfig is defined for two bidders; expect to see only appnexus in bidders FPD and contain site and user data", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + } + }, + "user": { + "id": "reqUserId" + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnFpdSiteId", + "page": "http://www.foobar.com/4321.html", + "name": "apnFpdSiteIdSiteName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "apnFpdSiteIdContentTitle", + "season": "apnFpdSiteIdContentSeason", + "data": [ + { + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" + }, + { + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" + } + ] + } + }, + "user": { + "id": "apnUserId", + "gender": "F", + "yob": 2000, + "keywords": "apnFpdUserKeyword", + "data": [ + { + "id": "apnFpdUserContentDataId1", + "name": "apnFpdUserContentDataName1" + }, + { + "id": "apnFpdUserContentDataId2", + "name": "apnFpdUserContentDataName2" + } + ] + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "id": "telariaFpdUserId", + "gender": "M", + "yob": 2001, + "keywords": "telariaFpdUserKeyword", + "data": [ + { + "id": "telariaFpdUserContentDataId1", + "name": "telariaFpdUserContentDataName1" + }, + { + "id": "telariaFpdUserContentDataId2", + "name": "telariaFpdUserContentDataName2" + } + ] + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "name": "reqSiteName", + "cat": [ + "electronics", + "phone" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/4321.html", + "name": "apnFpdSiteIdSiteName", + "cat": [ + "books", + "news" + ], + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "reqContentTitle", + "season": "reqContentSeason", + "data": [ + { + "id": "reqContentDataId1", + "name": "reqContentDataName1" + }, + { + "id": "reqContentDataId2", + "name": "reqContentDataName2" + } + ] + }, + "ext": { + "data": { + "id": "apnFpdSiteId", + "publisher": { + "id": "1" + }, + "content": { + "len": 900, + "title": "apnFpdSiteIdContentTitle", + "season": "apnFpdSiteIdContentSeason", + "data": [ + { + "id": "apnFpdSiteIdContentDataId1", + "name": "apnFpdSiteIdContentDataName1" + }, + { + "id": "apnFpdSiteIdContentDataId2", + "name": "apnFpdSiteIdContentDataName2" + } + ] + } + } + } + }, + "user": { + "id": "reqUserId", + "gender": "F", + "yob": 2000, + "keywords": "apnFpdUserKeyword", + "ext": { + "data": { + "id": "apnUserId", + "data": [ + { + "id": "apnFpdUserContentDataId1", + "name": "apnFpdUserContentDataName1" + }, + { + "id": "apnFpdUserContentDataId2", + "name": "apnFpdUserContentDataName2" + } + ] + } + } + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-no-global-bidder-list.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-no-global-bidder-list.json new file mode 100644 index 00000000000..d86db6dc4e4 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-no-global-bidder-list.json @@ -0,0 +1,98 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders is not defined; req.ext.prebid.bidderconfig is defined for two bidders. Expect to have both bidders in resoved FPD", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "data": "fpdGlobalSiteData" + } + }, + "user": { + "id": "reqUserId" + }, + "test": 1, + "ext": { + "prebid": { + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + }, + "user": { + "id": "apnUserId" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "user": { + "keywords": "telariaUserKeywords" + } + } + } + } + ] + } + } + }, + "outputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "data": "fpdGlobalSiteData" + } + }, + "test": 1 + }, + "biddersFPDResolved": { + "appnexus": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "ext": { + "data":{ + "id":"apnSiteId" + } + } + }, + "user": { + "id": "reqUserId", + "ext": { + "data":{ + "id":"apnUserId" + } + } + } + }, + "telaria": { + "user": { + "id": "reqUserId", + "keywords": "telariaUserKeywords" + } + } + }, + "validationErrors": [] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-one-with-incorrect-fpd.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-one-with-incorrect-fpd.json new file mode 100644 index 00000000000..a473fb9c0f3 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-one-with-incorrect-fpd.json @@ -0,0 +1,54 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders has appnexus and telaria bidders; req.ext.prebid.bidderconfig is defined for both bidders from req.ext.prebid.data.bidders; req.ext.prebid.bidderconfig for telaria has app config in it; expect to have validation error about app object is not defined in request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus", "telaria" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "site": { + "id": "apnSiteId" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "app": { + "id": "telariaAppId" + } + } + } + } + ] + } + } + }, + "outputRequestData": {}, + "biddersFPDResolved": {}, + "validationErrors": [ + {"Message": "incorrect First Party Data for bidder telaria: App object is not defined in request, but defined in FPD config"} + ] +} diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json new file mode 100644 index 00000000000..a23b45397f4 --- /dev/null +++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json @@ -0,0 +1,59 @@ +{ + "description": "req.site is present; req.ext.prebid.data.bidders has appnexus and telaria bidders; req.ext.prebid.bidderconfig is defined for both of them; req.ext.prebid.bidderconfig for appnexus contains app and user; req.ext.prebid.bidderconfig for telaria contains app; expected to have error for appnexus: app is not present in request, user is not present in request; expect to have error for telaria: app is not present in request", + "inputRequestData": { + "id": "bid_id", + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "test": 1, + "ext": { + "prebid": { + "data": { + "bidders": [ + "appnexus", "telaria" + ] + }, + "bidderconfig": [ + { + "bidders": [ + "appnexus" + ], + "config": { + "ortb2": { + "app": { + "id": "apnAppId" + }, + "user": { + "id": "apnUserId" + } + } + } + }, + { + "bidders": [ + "telaria" + ], + "config": { + "ortb2": { + "app": { + "id": "telariaAppId" + } + } + } + } + ] + } + } + }, + "outputRequestData":{}, + "biddersFPDResolved": {}, + "validationErrors": [ + {"Message": "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config"}, + {"Message": "incorrect First Party Data for bidder appnexus: App object is not defined in request, but defined in FPD config"}, + {"Message": "incorrect First Party Data for bidder telaria: App object is not defined in request, but defined in FPD config"} + ] +} diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json new file mode 100644 index 00000000000..db579fb2a41 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-app.json @@ -0,0 +1,44 @@ +{ + "description": "Bidder FPD defined only for app", + "inputRequestData": { + "app": { + "id": "reqUserID" + } + }, + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpddata": "appFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": { + "app": { + "id": "reqUserID", + "ext": { + "data": { + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpddata": "appFpddata", + "moreFpd": { + "fpd": 123 + } + } + }, + "id": "apnAppId" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json new file mode 100644 index 00000000000..86a40f29d75 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-site.json @@ -0,0 +1,44 @@ +{ + "description": "Bidder FPD defined only for site", + "inputRequestData": { + "site": { + "id": "reqUserID" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": { + "site": { + "id": "reqUserID", + "ext": { + "data": { + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + }, + "id": "apnSiteId" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json b/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json new file mode 100644 index 00000000000..a1fb4b4190f --- /dev/null +++ b/firstpartydata/tests/resolvefpd/bidder-fpd-only-user.json @@ -0,0 +1,49 @@ +{ + "description": "Bidder FPD defined only for user", + "inputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "ext": { + "data": { + "morefpdData": "morefpddata", + "userFpddata": "userFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "ext": { + "data": { + "morefpdData": "morefpddata", + "userFpddata": "userFpddata", + "moreFpd": { + "fpd": 123 + } + } + }, + "id": "apnUserId" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json new file mode 100644 index 00000000000..ebba9041788 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app-content-data-user-data.json @@ -0,0 +1,114 @@ +{ + "description": "Global FPD defined for app and user. Bidder FPD defined for app. Global FPD has app.content.data and user.data", + "inputRequestData": { + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpddata": "appFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "app": { + "appFpd": 123 + }, + "user": { + "testUserFpd": "testuser" + }, + "appContentData": [ + { + "id": "appData1", + "name": "appName1" + }, + { + "id": "appData2", + "name": "appName2" + } + ], + "userData": [ + { + "id": "userData1", + "name": "userName1" + }, + { + "id": "userData2", + "name": "userName2" + } + ] + }, + "outputRequestData": { + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "appData1", + "name": "appName1" + }, + { + "id": "appData2", + "name": "appName2" + } + ] + }, + "ext": { + "data": { + "moreFpd": { + "fpd": 123 + }, + "id": "apnAppId", + "morefpdData": "morefpddata", + "appFpd": 123, + "appFpddata": "appFpddata" + } + } + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "data": [ + { + "id": "userData1", + "name": "userName1" + }, + { + "id": "userData2", + "name": "userName2" + } + ], + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json new file mode 100644 index 00000000000..dea874147ab --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-app.json @@ -0,0 +1,80 @@ +{ + "description": "Global and bidder FPD defined for app", + "inputRequestData": { + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + } + }, + "bidderConfigFPD": { + "appnexus": { + "app": { + "id": "apnAppId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "appFpddata": "appFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "app": { + "appFpd": 123 + } + }, + "outputRequestData": { + "app": { + "id": "reqAppId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "id": "apnAppId", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "appFpd": 123, + "appFpddata": "appFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-app-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-app-user.json new file mode 100644 index 00000000000..73f827888ed --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-app-user.json @@ -0,0 +1,130 @@ +{ + "description": "Global and bidder FPD defined for site and user. Global FPD has site.content.data", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "testSiteExt": 123 + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "testSiteExt": 123, + "data": { + "id": "apnSiteId", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-content-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-content-data.json new file mode 100644 index 00000000000..2843834966e --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-content-data.json @@ -0,0 +1,144 @@ +{ + "description": "Global and bidder FPD defined for site. Global FPD has site.content.data", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "name": "reqSiteName", + "domain": "reqSiteDomain", + "cat": [ + "books", + "novels" + ], + "search": "book search", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "reqSiteData", + "name": "reqSiteName" + } + ] + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "name": "apnSiteName", + "domain": "apnSiteDomain", + "page": "http://www.foobar.com/1234.html", + "content": { + "episode": 7, + "title": "apnEpisodeName", + "len": 600, + "data": [ + { + "id": "siteData3", + "name": "siteName3" + } + ] + }, + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "site": { + "testSiteFpd": "testSite" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "name": "apnSiteName", + "page": "http://www.foobar.com/1234.html", + "domain": "apnSiteDomain", + "cat": [ + "books", + "novels" + ], + "search": "book search", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900, + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "data": { + "id": "apnSiteId", + "content": { + "episode": 7, + "title": "apnEpisodeName", + "len": 600, + "data": [ + { + "id": "siteData3", + "name": "siteName3" + } + ] + }, + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "testSiteFpd": "testSite", + "moreFpd": { + "fpd": 123 + } + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json new file mode 100644 index 00000000000..fc65518dcd3 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site-user.json @@ -0,0 +1,96 @@ +{ + "description": "Global FPD defined for user and bidder FPD defined for site and user", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + } + }, + "user": { + "id": "apnUserId" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + }, + "user": { + "id": "apnUserId", + "ext": { + "data": { + "moreFpd": { + "fpd": 567 + } + } + } + } + } + }, + "globalFPD": { + "user": { + "testUserFpd": "testuser" + } + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + }, + "id": "apnSiteId" + } + } + }, + "user": { + "id": "apnUserId", + "ext": { + "data": { + "id": "apnUserId", + "moreFpd": { + "fpd": 567 + }, + "testUserFpd": "testuser" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json new file mode 100644 index 00000000000..f3d3fb74df2 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-site.json @@ -0,0 +1,71 @@ +{ + "description": "Global and bidder FPD defined for site", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + } + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + } + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + }, + "ext": { + "data": { + "id": "apnSiteId", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json new file mode 100644 index 00000000000..612518655d9 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-fpd-user.json @@ -0,0 +1,51 @@ +{ + "description": "Global and bidder FPD defined for user", + "inputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "user": { + "id": "apnUserId", + "yob": 1982, + "ext": { + "data": { + "morefpdData": "morefpddata", + "userFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "user": { + "testUserFpd": "testuser" + } + }, + "outputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "id": "apnUserId", + "testUserFpd": "testuser", + "morefpdData": "morefpddata", + "userFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json b/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json new file mode 100644 index 00000000000..d0154d1bae6 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/global-and-bidder-site-content-data.json @@ -0,0 +1,114 @@ +{ + "description": "Global FPD defined for site, app, and user and bidder FPD defined for site. Global FPD has site.content.data", + "inputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "id": "apnSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "globalFPD": { + "site": { + "siteFpd": 123 + }, + "app": { + "appFpd": { + "testValue": true + } + }, + "user": { + "testUserFpd": "testuser" + }, + "siteContentData": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "outputRequestData": { + "site": { + "id": "reqSiteId", + "page": "http://www.foobar.com/1234.html", + "ref": "fpdRef", + "publisher": { + "id": "1" + }, + "content": { + "data": [ + { + "id": "siteData1", + "name": "siteName1" + }, + { + "id": "siteData2", + "name": "siteName2" + } + ] + }, + "ext": { + "data": { + "id": "apnSiteId", + "moreFpd": { + "fpd": 123 + }, + "morefpdData": "morefpddata", + "siteFpd": 123, + "siteFpddata": "siteFpddata" + } + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M", + "ext": { + "data": { + "testUserFpd": "testuser" + } + } + } + } +} + diff --git a/firstpartydata/tests/resolvefpd/req-app-not-defined.json b/firstpartydata/tests/resolvefpd/req-app-not-defined.json new file mode 100644 index 00000000000..f24a4d27db1 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/req-app-not-defined.json @@ -0,0 +1,46 @@ +{ + "description": "Incorrect bidder specific FPD: app not defined in request", + "inputRequestData": { + "site": { + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + }, + "content": { + "episode": 6, + "title": "episodeName", + "series": "TvName", + "season": "season3", + "len": 900 + } + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "app": { + "page": "", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": {}, + "validationErrors": [ + { + "Message": "incorrect First Party Data for bidder appnexus: App object is not defined in request, but defined in FPD config" + } + ] +} + diff --git a/firstpartydata/tests/resolvefpd/req-site-not-defined.json b/firstpartydata/tests/resolvefpd/req-site-not-defined.json new file mode 100644 index 00000000000..d078ad7804f --- /dev/null +++ b/firstpartydata/tests/resolvefpd/req-site-not-defined.json @@ -0,0 +1,33 @@ +{ + "description": "Incorrect bidder specific FPD: site not defined in request", + "inputRequestData": { + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "page": "", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": {}, + "validationErrors": [ + { + "Message": "incorrect First Party Data for bidder appnexus: Site object is not defined in request, but defined in FPD config" + } + ] +} + diff --git a/firstpartydata/tests/resolvefpd/req-user-not-defined.json b/firstpartydata/tests/resolvefpd/req-user-not-defined.json new file mode 100644 index 00000000000..76ae3f827ca --- /dev/null +++ b/firstpartydata/tests/resolvefpd/req-user-not-defined.json @@ -0,0 +1,31 @@ +{ + "description": "Incorrect bidder specific FPD: user not defined in request", + "inputRequestData": { + "test": 1, + "at": 1, + "tmax": 5000 + }, + "bidderConfigFPD": { + "appnexus": { + "user": { + "keywords": "test user keywords", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": {}, + "validationErrors": [ + { + "Message": "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config" + } + ] +} + diff --git a/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json b/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json new file mode 100644 index 00000000000..8da3dc69329 --- /dev/null +++ b/firstpartydata/tests/resolvefpd/site-page-empty-conflict.json @@ -0,0 +1,45 @@ +{ + "description": "Incorrect bidder specific FPD: site has no id and attempts to overwrite page to empty", + "inputRequestData": { + "site": { + "page": "http://www.foobar.com/1234.html", + "publisher": { + "id": "1" + } + }, + "device": { + "ua": "testDevice", + "ip": "123.145.167.10", + "devicetype": 1, + "ifa": "123" + }, + "user": { + "id": "reqUserID", + "yob": 1982, + "gender": "M" + } + }, + "bidderConfigFPD": { + "appnexus": { + "site": { + "page": "", + "ext": { + "data": { + "morefpdData": "morefpddata", + "siteFpddata": "siteFpddata", + "moreFpd": { + "fpd": 123 + } + } + } + } + } + }, + "outputRequestData": {}, + "validationErrors": [ + { + "Message": "incorrect First Party Data for bidder appnexus: Site object cannot set empty page if req.site.id is empty" + } + ] +} + diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index bf875123ea1..d399715f4d8 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -44,6 +44,22 @@ type ExtRequestPrebid struct { NoSale []string `json:"nosale,omitempty"` CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` + BidderConfigs []BidderConfig `json:"bidderconfig,omitempty"` +} + +type BidderConfig struct { + Bidders []string `json:"bidders,omitempty"` + Config *Config `json:"config,omitempty"` +} + +type Config struct { + ORTB2 *ORTB2 `json:"ortb2,omitempty"` +} + +type ORTB2 struct { //First party data + Site map[string]json.RawMessage `json:"site,omitempty"` + App map[string]json.RawMessage `json:"app,omitempty"` + User map[string]json.RawMessage `json:"user,omitempty"` } type ExtRequestCurrency struct { @@ -318,6 +334,7 @@ var priceGranularityAuto = PriceGranularity{ // ExtRequestPrebidData defines Prebid's First Party Data (FPD) and related bid request options. type ExtRequestPrebidData struct { EidPermissions []ExtRequestPrebidDataEidPermission `json:"eidpermissions"` + Bidders []string `json:"bidders,omitempty"` } // ExtRequestPrebidDataEidPermission defines a filter rule for filter user.ext.eids diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go index 276f8f5eebe..9219bbdbc9e 100644 --- a/openrtb_ext/request_wrapper.go +++ b/openrtb_ext/request_wrapper.go @@ -210,7 +210,7 @@ func (rw *RequestWrapper) rebuildSiteExt() error { if err != nil { return err } - rw.Regs.Ext = siteJson + rw.Site.Ext = siteJson } return nil } diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go index 4fd56745301..d8716297faf 100644 --- a/util/jsonutil/jsonutil.go +++ b/util/jsonutil/jsonutil.go @@ -8,12 +8,18 @@ import ( var comma = []byte(",")[0] var colon = []byte(":")[0] +var sqBracket = []byte("]")[0] +var openCurlyBracket = []byte("{")[0] +var closingCurlyBracket = []byte("}")[0] +var quote = []byte(`"`)[0] -func findElementIndexes(extension []byte, elementName string) (bool, int64, int64, error) { - found := false +//Finds element in json byte array with any level of nesting +func FindElement(extension []byte, elementNames ...string) (bool, int64, int64, error) { + elementName := elementNames[0] buf := bytes.NewBuffer(extension) dec := json.NewDecoder(buf) - var startIndex int64 + found := false + var startIndex, endIndex int64 var i interface{} for { token, err := dec.Token() @@ -24,21 +30,18 @@ func findElementIndexes(extension []byte, elementName string) (bool, int64, int6 if err != nil { return false, -1, -1, err } - if token == elementName { err := dec.Decode(&i) if err != nil { return false, -1, -1, err } - found = true - endIndex := dec.InputOffset() + 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 { @@ -48,43 +51,62 @@ func findElementIndexes(extension []byte, elementName string) (bool, int64, int6 endIndex++ } } - return found, startIndex, endIndex, nil + found = true + break } else { startIndex = dec.InputOffset() } - } - - return false, -1, -1, nil -} - -func DropElement(extension []byte, elementName string) ([]byte, error) { - found, startIndex, endIndex, err := findElementIndexes(extension, elementName) if found { - extension = append(extension[:startIndex], extension[endIndex:]...) - } - return extension, err -} - -func FindElement(extension []byte, elementName string) (bool, []byte, error) { + if len(elementNames) == 1 { + return found, startIndex, endIndex, nil + } else if len(elementNames) > 1 { + for { + //find the beginning of nested element + if extension[startIndex] == colon { + startIndex++ + break + } + startIndex++ + } + for { + if endIndex == int64(len(extension)) { + endIndex-- + } - found, startIndex, endIndex, err := findElementIndexes(extension, elementName) + //if structure had more elements, need to find index of comma at the end + if extension[endIndex] == sqBracket || extension[endIndex] == closingCurlyBracket { + break + } - if found && err == nil { - element := extension[startIndex:endIndex] - index := 0 - for { - if index < len(element) && element[index] != colon { - index++ - } else { - index++ - break + if extension[endIndex] == comma { + endIndex-- + break + } else { + endIndex-- + } } + if found { + found, startInd, endInd, err := FindElement(extension[startIndex:endIndex], elementNames[1:]...) + return found, startIndex + startInd, startIndex + endInd, err + } + return found, startIndex, startIndex, nil } - element = element[index:] - return found, element, err } + return found, startIndex, endIndex, nil +} - return found, nil, err - +//Drops element from json byte array +// - Doesn't support drop element from json list +// - Keys in the path can skip levels +// - First found element will be removed +func DropElement(extension []byte, elementNames ...string) ([]byte, error) { + found, startIndex, endIndex, err := FindElement(extension, elementNames...) + if err != nil { + return nil, err + } + if found { + extension = append(extension[:startIndex], extension[endIndex:]...) + } + return extension, nil } diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go index d8738e171c7..12b1fd5e803 100644 --- a/util/jsonutil/jsonutil_test.go +++ b/util/jsonutil/jsonutil_test.go @@ -7,300 +7,179 @@ import ( ) func TestDropElement(t *testing.T) { + tests := []struct { description string input []byte - elementToRemove string + elementToRemove []string output []byte errorExpected bool errorContains string }{ { - description: "Drop single element after another element", + description: "Drop Single Element After Another Element", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`), - elementToRemove: "consented_providers", + elementToRemove: []string{"consented_providers"}, output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`), errorExpected: false, errorContains: "", }, { - description: "Drop single element before another element", + description: "Drop Single Element Before Another Element", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`), - elementToRemove: "consented_providers", + elementToRemove: []string{"consented_providers"}, output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`), errorExpected: false, errorContains: "", }, { - description: "Drop single element", + description: "Drop Single Element", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`), - elementToRemove: "consented_providers", + elementToRemove: []string{"consented_providers"}, output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`), errorExpected: false, errorContains: "", }, { - description: "Drop single element string", + description: "Drop Single Element string", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`), - elementToRemove: "consented_providers", + elementToRemove: []string{"consented_providers"}, output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`), errorExpected: false, errorContains: "", }, { - description: "Drop parent element between two elements", + 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", + elementToRemove: []string{"consented_providers_settings"}, output: []byte(`{"consent": "TESTCONSENT","test": 123}`), errorExpected: false, errorContains: "", }, { - description: "Drop parent element before element", + description: "Drop Parent Element Before Element", input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), - elementToRemove: "consented_providers_settings", + elementToRemove: []string{"consented_providers_settings"}, output: []byte(`{"test": 123}`), errorExpected: false, errorContains: "", }, { - description: "Drop parent element after element", + description: "Drop Parent Element After Element", input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToRemove: "consented_providers_settings", + elementToRemove: []string{"consented_providers_settings"}, output: []byte(`{"consent": "TESTCONSENT"}`), errorExpected: false, errorContains: "", }, { - description: "Drop parent element only", + description: "Drop Parent Element Only", input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToRemove: "consented_providers_settings", + elementToRemove: []string{"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}}`), + description: "Drop Parent Element List", + input: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"test":1},"data": [{"test1":5},{"test2": [1,2,3]}]}`), + elementToRemove: []string{"data"}, + 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") - } - - } -} - -func TestFindElement(t *testing.T) { - tests := []struct { - description string - input []byte - elementToFind string - output []byte - elementFound bool - }{ - { - description: "Find array element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers":[1608,765,492]}}`), - elementToFind: "consented_providers", - output: []byte(`[1608,765,492]`), - elementFound: true, - }, - { - description: "Find object element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings":{"test": 1,"consented_providers": [1608,765,492]}}`), - elementToFind: "consented_providers_settings", - output: []byte(`{"test": 1,"consented_providers": [1608,765,492]}`), - elementFound: true, - }, - { - description: "Find element that doesn't exist", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings":{"test": 1,"consented_providers": [1608,765,492]}}`), - elementToFind: "test_element", - output: []byte(nil), - elementFound: false, - }, - } - - for _, tt := range tests { - exists, res, err := FindElement(tt.input, tt.elementToFind) - assert.NoError(t, err, "Error should be nil") - assert.Equal(t, tt.output, res, "Result is incorrect") - - if tt.elementFound { - assert.True(t, exists, "Element must be found") - } else { - assert.False(t, exists, "Element must not be found") - } - } -} - -func TestFindElementIndexes(t *testing.T) { - tests := []struct { - description string - input []byte - elementToFind string - startIndex int64 - endIndex int64 - found bool - errorExpected bool - errorContains string - }{ - { - description: "Find single element after another element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`), - elementToFind: "consented_providers", - startIndex: 68, - endIndex: 106, - found: true, - errorExpected: false, - errorContains: "", - }, - { - description: "Find single element before another element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`), - elementToFind: "consented_providers", - startIndex: 59, - endIndex: 97, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Element That Doesn't Exist", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToRemove: []string{"test2"}, + output: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + errorExpected: false, + errorContains: "", }, { - description: "Find single element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`), - elementToFind: "consented_providers", - startIndex: 59, - endIndex: 98, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Element Single Occurrence", + input: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"test":1},"data": [{"test1":5},{"test2": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers_settings", "test"}, + output: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492]},"data": [{"test1":5},{"test2": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find single element string", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`), - elementToFind: "consented_providers", - startIndex: 59, - endIndex: 88, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Element Multiple Occurrence", + input: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"test":1},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers_settings", "test"}, + output: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492]},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find parent element between two elements", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), - elementToFind: "consented_providers_settings", - startIndex: 26, - endIndex: 109, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Element Multiple Occurrence Skip Path", + input: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"data": {"amp":1, "test": 25}}}`), + elementToRemove: []string{"consented_providers_settings", "test"}, + output: []byte(`{"consented_providers_settings":{"consented_providers":[1608,765,492],"data": {"amp":1}}}`), + errorExpected: false, + errorContains: "", }, { - description: "Find parent element before element", - input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), - elementToFind: "consented_providers_settings", - startIndex: 1, - endIndex: 84, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Structure Single Occurrence", + input: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested":true}},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers", "test"}, + output: []byte(`{"consented_providers":{"providers":[1608,765,492]},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find parent element after element", - input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToFind: "consented_providers_settings", - startIndex: 25, - endIndex: 108, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Structure Single Occurrence Deep Nested", + input: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested":true, "nested2": {"test6": 123}}},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers", "test6"}, + output: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested":true, "nested2": {}}},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find parent element only", - input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToFind: "consented_providers_settings", - startIndex: 1, - endIndex: 83, - found: true, - errorExpected: false, - errorContains: "", + description: "Drop Nested Structure Single Occurrence Deep Nested Full Path", + input: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested":true,"nested2": {"test6": 123}}},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers", "test", "nested"}, + output: []byte(`{"consented_providers":{"providers":[1608,765,492],"test":{"nested2": {"test6": 123}}},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, { - description: "Find element that doesn't exist", - input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), - elementToFind: "test2", - startIndex: -1, - endIndex: -1, - found: false, - errorExpected: false, - errorContains: "", + description: "Drop Nested Structure Doesn't Exist", + input: []byte(`{"consented_providers":{"providers":[1608,765,492]},"test":{"nested":true}},"data": [{"test":5},{"test": [1,2,3]}]}`), + elementToRemove: []string{"consented_providers", "test2"}, + output: []byte(`{"consented_providers":{"providers":[1608,765,492]},"test":{"nested":true}},"data": [{"test":5},{"test": [1,2,3]}]}`), + errorExpected: false, + errorContains: "", }, //Errors { - description: "Error decode", - input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`), - elementToFind: "consented_providers", - startIndex: -1, - endIndex: -1, - found: false, - errorExpected: true, - errorContains: "looking for beginning of value", + description: "Error Decode", + input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`), + elementToRemove: []string{"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}}`), - elementToFind: "consented_providers", - startIndex: -1, - endIndex: -1, - found: false, - errorExpected: true, - errorContains: "invalid character", + description: "Error Malformed", + input: []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`), + elementToRemove: []string{"consented_providers"}, + output: []byte(``), + errorExpected: true, + errorContains: "invalid character", }, } for _, tt := range tests { - found, start, end, err := findElementIndexes(tt.input, tt.elementToFind) - - assert.Equal(t, tt.found, found, "Incorrect value of element existence") + 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.startIndex, start, "Result is incorrect") - assert.Equal(t, tt.endIndex, end, "Result is incorrect") + assert.Equal(t, tt.output, res, "Result is incorrect") } - } } From 9abefb44c41a1914ca0e07211dd9e392704624bf Mon Sep 17 00:00:00 2001 From: Katarzyna-B <48512286+Katarzyna-B@users.noreply.github.com> Date: Thu, 4 Nov 2021 18:18:56 +0100 Subject: [PATCH 129/140] Smaato: Add userSync url (#2064) --- static/bidder-info/smaato.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml index b73edb23d18..a070db1d7d3 100644 --- a/static/bidder-info/smaato.yaml +++ b/static/bidder-info/smaato.yaml @@ -10,3 +10,7 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + url: "https://s.ad.smaato.net/c/?adExInit=p&redir={{.RedirectURL}}" + userMacro: "$UID" From b3fea58b934fe160e816c01af48010cff861c291 Mon Sep 17 00:00:00 2001 From: Gena Date: Thu, 4 Nov 2021 19:20:15 +0200 Subject: [PATCH 130/140] add streamkey alias (#2067) --- config/config.go | 1 + exchange/adapter_builders.go | 1 + openrtb_ext/bidders.go | 2 ++ static/bidder-info/streamkey.yaml | 14 ++++++++++++++ static/bidder-params/streamkey.json | 26 ++++++++++++++++++++++++++ 5 files changed, 44 insertions(+) create mode 100644 static/bidder-info/streamkey.yaml create mode 100644 static/bidder-params/streamkey.json diff --git a/config/config.go b/config/config.go index 49ca9589d17..5a20e257b73 100644 --- a/config/config.go +++ b/config/config.go @@ -877,6 +877,7 @@ func SetupViper(v *viper.Viper, filename string) { 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") + v.SetDefault("adapters.streamkey.endpoint", "http://ghb.hb.streamkey.net/pbs/ortb") v.SetDefault("adapters.synacormedia.endpoint", "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}") v.SetDefault("adapters.tappx.endpoint", "http://{{.Host}}") v.SetDefault("adapters.telaria.endpoint", "https://ads.tremorhub.com/ad/rtb/prebid") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index db55e733ee4..33d662c2ef7 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -249,6 +249,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderSomoaudience: somoaudience.Builder, openrtb_ext.BidderSonobi: sonobi.Builder, openrtb_ext.BidderSovrn: sovrn.Builder, + openrtb_ext.BidderStreamkey: adtelligent.Builder, openrtb_ext.BidderSynacormedia: synacormedia.Builder, openrtb_ext.BidderTappx: tappx.Builder, openrtb_ext.BidderTelaria: telaria.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 23ec7fdeaf5..d5c424c5ead 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -183,6 +183,7 @@ const ( BidderSomoaudience BidderName = "somoaudience" BidderSonobi BidderName = "sonobi" BidderSovrn BidderName = "sovrn" + BidderStreamkey BidderName = "streamkey" BidderSynacormedia BidderName = "synacormedia" BidderTappx BidderName = "tappx" BidderTelaria BidderName = "telaria" @@ -321,6 +322,7 @@ func CoreBidderNames() []BidderName { BidderSomoaudience, BidderSonobi, BidderSovrn, + BidderStreamkey, BidderSynacormedia, BidderTappx, BidderTelaria, diff --git a/static/bidder-info/streamkey.yaml b/static/bidder-info/streamkey.yaml new file mode 100644 index 00000000000..a13b71e588a --- /dev/null +++ b/static/bidder-info/streamkey.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "contact@streamkey.tv" +capabilities: + app: + mediaTypes: + - video + site: + mediaTypes: + - video +userSync: + # streamkey supports user syncing, but requires configuration by the host. contact this + # bidder directly at the email address in this file to ask about enabling user sync. + supports: + - iframe diff --git a/static/bidder-params/streamkey.json b/static/bidder-params/streamkey.json new file mode 100644 index 00000000000..ec8e5b1b643 --- /dev/null +++ b/static/bidder-params/streamkey.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Streamkey Adapter Params", + "description": "A schema which validates params accepted by the Streamkey 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"] +} From 8ed7cc3b29e89b58e062b12fefef8c6942b6a1cd Mon Sep 17 00:00:00 2001 From: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> Date: Wed, 10 Nov 2021 10:45:45 -0500 Subject: [PATCH 131/140] Triplelift: adds iframe user sync to yaml (#2068) --- static/bidder-info/triplelift.yaml | 9 ++++++++- static/bidder-info/triplelift_native.yaml | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml index 99026b6f171..84eea8690f1 100644 --- a/static/bidder-info/triplelift.yaml +++ b/static/bidder-info/triplelift.yaml @@ -11,6 +11,13 @@ capabilities: - banner - video userSync: + # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. + # Contact this bidder directly at the email address above to ask about enabling user sync. + # + default: iframe + iframe: + url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: $UID redirect: url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" - userMacro: "$UID" + userMacro: "$UID" \ No newline at end of file diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml index 32eb07efa14..dc60d572d05 100644 --- a/static/bidder-info/triplelift_native.yaml +++ b/static/bidder-info/triplelift_native.yaml @@ -9,6 +9,13 @@ capabilities: mediaTypes: - native userSync: + # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. + # Contact this bidder directly at the email address above to ask about enabling user sync. + # + default: iframe + iframe: + url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" + userMacro: $UID redirect: url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}" userMacro: "$UID" \ No newline at end of file From 295bcf83374df20502cbe4ec8f1113d6cf5a0e46 Mon Sep 17 00:00:00 2001 From: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Date: Wed, 10 Nov 2021 10:48:20 -0800 Subject: [PATCH 132/140] Address stored request + macro id bug (#2059) --- endpoints/openrtb2/auction.go | 16 ++++----- endpoints/openrtb2/auction_test.go | 52 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index cd383c25179..ec9e41525a8 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1377,12 +1377,12 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson // Apply the Stored BidRequest, if it exists resolvedRequest := requestJson - if deps.cfg.GenerateRequestID || bidRequestID == "{{UUID}}" { + if hasStoredBidRequest { isAppRequest, err := checkIfAppRequest(requestJson) if err != nil { return nil, nil, []error{err} } - if isAppRequest && hasStoredBidRequest { + if isAppRequest && (deps.cfg.GenerateRequestID || bidRequestID == "{{UUID}}") { uuidPatch, err := generateUuidForBidRequest(deps.uuidGenerator) if err != nil { return nil, nil, []error{err} @@ -1397,12 +1397,12 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) return nil, nil, errL } - } - } else if hasStoredBidRequest { - resolvedRequest, err = jsonpatch.MergePatch(storedRequests[storedBidRequestId], requestJson) - if err != nil { - errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) - return nil, nil, errL + } else { + resolvedRequest, err = jsonpatch.MergePatch(storedRequests[storedBidRequestId], requestJson) + if err != nil { + errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) + return nil, nil, errL + } } } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 5b234b12c8b..8439cc65764 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1174,6 +1174,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) { givenRawData string givenGenerateRequestID bool expectedID string + expectedCur string }{ { description: "GenerateRequestID is true, rawData is an app request and has stored bid request we should generate uuid", @@ -1217,6 +1218,20 @@ func TestStoredRequestGenerateUuid(t *testing.T) { givenGenerateRequestID: false, expectedID: "ThisID", }, + { + description: "Test to check that stored requests are being merged when Macro ID is present with a site rquest", + givenRawData: testBidRequests[5], + givenGenerateRequestID: false, + expectedID: "ThisID", + expectedCur: "USD", + }, + { + description: "Test to check that stored requests are being merged when Generate Request ID flag with a site rquest", + givenRawData: testBidRequests[5], + givenGenerateRequestID: true, + expectedID: "ThisID", + expectedCur: "USD", + }, } for _, test := range testCases { @@ -1229,6 +1244,9 @@ func TestStoredRequestGenerateUuid(t *testing.T) { if err := json.Unmarshal(newRequest, req); err != nil { t.Errorf("processStoredRequests Error: %s", err.Error()) } + if test.expectedCur != "" { + assert.Equalf(t, test.expectedCur, req.Cur[0], "The stored request wasn't merged properly: %s\n", test.description) + } assert.Equalf(t, test.expectedID, req.ID, "The Bid Request ID is incorrect: %s\n", test.description) } } @@ -3185,6 +3203,7 @@ var testStoredRequestData = map[string]json.RawMessage{ } }} }`), + "4": json.RawMessage(`{"id": "{{UUID}}", "cur": ["USD"]}`), } // Stored Imp Requests @@ -3762,6 +3781,39 @@ var testBidRequests = []string{ } } }`, + `{ + "id": "ThisID", + "imp": [{ + "id": "some-impression-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }], + "ext": { + "prebid": { + "debug": true, + "storedrequest": { + "id": "4" + } + } + }, + "site": { + "page": "https://example.com" + } + }`, } type mockStoredReqFetcher struct { From e420e9302cbac6bb15766875b61b5121b102411a Mon Sep 17 00:00:00 2001 From: ramyferjaniadot <90328697+ramyferjaniadot@users.noreply.github.com> Date: Wed, 10 Nov 2021 19:58:29 +0100 Subject: [PATCH 133/140] Adot: Add gvlVendorID to adapter --- static/bidder-info/adot.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/static/bidder-info/adot.yaml b/static/bidder-info/adot.yaml index 6a7aca06881..cd2cd3c4f73 100644 --- a/static/bidder-info/adot.yaml +++ b/static/bidder-info/adot.yaml @@ -1,5 +1,6 @@ maintainer: email: "admin@we-are-adot.com" +gvlVendorID: 272 modifyingVastXmlAllowed: false capabilities: app: From b86be91ac14ca369b490e9e01915c828cd03bbb8 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Mon, 15 Nov 2021 13:32:25 -0500 Subject: [PATCH 134/140] Add a garbage collection threshold (#2081) --- config/config.go | 5 +++++ config/config_test.go | 2 ++ main.go | 9 +++++++++ 3 files changed, 16 insertions(+) diff --git a/config/config.go b/config/config.go index 5a20e257b73..83585146605 100644 --- a/config/config.go +++ b/config/config.go @@ -24,6 +24,10 @@ type Configuration struct { CacheClient HTTPClient `mapstructure:"http_client_cache"` AdminPort int `mapstructure:"admin_port"` EnableGzip bool `mapstructure:"enable_gzip"` + // GarbageCollectorThreshold allocates virtual memory (in bytes) which is not used by PBS but + // serves as a hack to trigger the garbage collector only when the heap reaches at least this size. + // More info: https://github.com/golang/go/issues/48409 + GarbageCollectorThreshold int `mapstructure:"garbage_collector_threshold"` // StatusResponse is the string which will be returned by the /status endpoint when things are OK. // If empty, it will return a 204 with no content. StatusResponse string `mapstructure:"status_response"` @@ -634,6 +638,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("port", 8000) v.SetDefault("admin_port", 6060) v.SetDefault("enable_gzip", false) + v.SetDefault("garbage_collector_threshold", 0) v.SetDefault("status_response", "") v.SetDefault("auction_timeouts_ms.default", 0) v.SetDefault("auction_timeouts_ms.max", 0) diff --git a/config/config_test.go b/config/config_test.go index 9e9c70375e4..f86f13bda7c 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -289,6 +289,7 @@ external_url: http://prebid-server.prebid.org/ host: prebid-server.prebid.org port: 1234 admin_port: 5678 +garbage_collector_threshold: 1 auction_timeouts_ms: max: 123 default: 50 @@ -426,6 +427,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "host", cfg.Host, "prebid-server.prebid.org") cmpInts(t, "port", cfg.Port, 1234) cmpInts(t, "admin_port", cfg.AdminPort, 5678) + cmpInts(t, "garbage_collector_threshold", cfg.GarbageCollectorThreshold, 1) cmpInts(t, "auction_timeouts_ms.default", int(cfg.AuctionTimeouts.Default), 50) cmpInts(t, "auction_timeouts_ms.max", int(cfg.AuctionTimeouts.Max), 123) cmpStrings(t, "cache.scheme", cfg.CacheURL.Scheme, "http") diff --git a/main.go b/main.go index 76fa64f77ef..c103863107d 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "flag" "math/rand" "net/http" + "runtime" "time" "github.com/prebid/prebid-server/config" @@ -28,6 +29,14 @@ func main() { glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err) } + // Create a soft memory limit on the total amount of memory that PBS uses to tune the behavior + // of the Go garbage collector. In summary, `cfg.GarbageCollectorThreshold` serves as a fixed cost + // of memory that is going to be held garbage before a garbage collection cycle is triggered. + // This amount of virtual memory won’t translate into physical memory allocation unless we attempt + // to read or write to the slice below, which PBS will not do. + garbageCollectionThreshold := make([]byte, cfg.GarbageCollectorThreshold) + defer runtime.KeepAlive(garbageCollectionThreshold) + err = serve(cfg) if err != nil { glog.Exitf("prebid-server failed: %v", err) From 36e6b404dfb4ad0958d2bccc1e6726334c1ffd57 Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Tue, 16 Nov 2021 15:51:42 +0100 Subject: [PATCH 135/140] bidRequest.Source.Ext wasn't cloned in prepareSource (#2070) --- exchange/utils.go | 3 ++ exchange/utils_test.go | 83 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/exchange/utils.go b/exchange/utils.go index 5a68ba45df9..58a3883602f 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -309,6 +309,9 @@ func prepareSource(req *openrtb2.BidRequest, bidder string, sChainsByBidder map[ // set source if req.Source == nil { req.Source = &openrtb2.Source{} + } else { + sourceCopy := *req.Source + req.Source = &sourceCopy } schain := openrtb_ext.ExtRequestPrebidSChain{ SChain: *selectedSChain, diff --git a/exchange/utils_test.go b/exchange/utils_test.go index ef7d0bdb4a1..7c52c88bf7e 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -5,12 +5,12 @@ import ( "encoding/json" "errors" "fmt" - "github.com/prebid/prebid-server/firstpartydata" "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/firstpartydata" "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" @@ -2609,8 +2609,87 @@ func TestBuildXPrebidHeader(t *testing.T) { } } -func TestApplyFPD(t *testing.T) { +func TestSourceExtSChainCopied(t *testing.T) { + bidRequest := newBidRequest(t) + + bidderSchains := map[string]*openrtb_ext.ExtRequestPrebidSChainSChain{ + "bidder1": { + Ver: "1.0", + Complete: 1, + Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{ + { + ASI: "bidder1.com", + SID: "0001", + HP: 1, + }, + }, + }, + "bidder2": { + Ver: "1.0", + Complete: 1, + Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{ + { + ASI: "bidder2.com", + SID: "0002", + HP: 1, + }, + }, + }, + } + + copy1 := *bidRequest + originalTid := copy1.Source.TID + prepareSource(©1, "bidder1", bidderSchains) + copy2 := *bidRequest + copy2.Source.TID = "new TID" + prepareSource(©2, "bidder2", bidderSchains) + + assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder1.com","sid":"0001","hp":1}],"ver":"1.0"}}`), copy1.Source.Ext, "First schain was overwritten or not set") + assert.Equal(t, originalTid, copy1.Source.TID, "Original TID was overwritten") + assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder2.com","sid":"0002","hp":1}],"ver":"1.0"}}`), copy2.Source.Ext, "Second schain was overwritten or not set") + assert.Equal(t, "new TID", copy2.Source.TID, "New TID was not set") +} +func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { + req := &openrtb2.BidRequest{ + Site: &openrtb2.Site{}, + Source: &openrtb2.Source{ + TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", + }, + Imp: []openrtb2.Imp{{ + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "axonix": { "supplyId": "123"}}`), + }}, + Ext: json.RawMessage(`{"prebid":{"schains":[{ "bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["axonix"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), + } + + unmarshaledExt, err := extractBidRequestExt(req) + assert.NoErrorf(t, err, "Error unmarshaling inExt") + extRequest := unmarshaledExt + + auctionReq := AuctionRequest{ + BidRequest: req, + UserSyncs: &emptyUsersync{}, + } + bidderToSyncerKey := map[string]string{} + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) + + assert.Nil(t, errs) + assert.Len(t, bidderRequests, 2, "Bid request count is not 2") + + bidRequestSourceExts := map[openrtb_ext.BidderName]json.RawMessage{} + for _, bidderRequest := range bidderRequests { + bidRequestSourceExts[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.Ext + } + + appnexusPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`) + axonixPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}`) + assert.Equal(t, appnexusPrebidSchainsSchain, bidRequestSourceExts["appnexus"], "Incorrect appnexus bid request schain in source.ext") + assert.Equal(t, axonixPrebidSchainsSchain, bidRequestSourceExts["axonix"], "Incorrect axonix bid request schain in source.ext") +} + +func TestApplyFPD(t *testing.T) { fpdBidderTest := map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{} bidderTest := openrtb_ext.BidderName("test") fpdBidderTest[bidderTest] = &firstpartydata.ResolvedFirstPartyData{Site: nil, App: nil, User: nil} From 7cc465c72718402d1a5a9361987c1bb168e3df69 Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Wed, 17 Nov 2021 23:58:18 +0530 Subject: [PATCH 136/140] PubMatic + OpenWrap: Support request level Bidder Params #1042 (#1991) --- adapters/openrtb_util.go | 61 +++++++ adapters/openrtb_util_test.go | 117 ++++++++++++ adapters/pubmatic/pubmatic.go | 103 +++++++++-- .../pubmatictest/supplemental/nilReqExt.json | 160 +++++++++++++++++ .../supplemental/reqBidderParams.json | 169 ++++++++++++++++++ endpoints/openrtb2/auction.go | 119 ++++++++++++ endpoints/openrtb2/auction_test.go | 114 ++++++++++++ .../invalid-whole/cache-nothing.json | 2 +- .../invalid-whole/imp-ext-invalid-params.json | 2 +- ...dder-params-backward-compatible-merge.json | 101 +++++++++++ ...-bidder-params-merge-reserved-keyword.json | 116 ++++++++++++ .../req-ext-bidder-params-merge.json | 104 +++++++++++ .../supplementary/req-ext-bidder-params.json | 98 ++++++++++ exchange/utils.go | 42 ++++- exchange/utils_test.go | 125 +++++++++++++ openrtb_ext/bidders.go | 5 + openrtb_ext/bidders_test.go | 3 + openrtb_ext/request.go | 1 + 18 files changed, 1415 insertions(+), 27 deletions(-) create mode 100644 adapters/openrtb_util.go create mode 100644 adapters/openrtb_util_test.go create mode 100644 adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go new file mode 100644 index 00000000000..5587cac7ef2 --- /dev/null +++ b/adapters/openrtb_util.go @@ -0,0 +1,61 @@ +package adapters + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" +) + +func ExtractAdapterReqBidderParams(bidRequest *openrtb2.BidRequest) (map[string]json.RawMessage, error) { + if bidRequest == nil { + return nil, errors.New("error bidRequest should not be nil") + } + + reqExt := &openrtb_ext.ExtRequest{} + if len(bidRequest.Ext) > 0 { + err := json.Unmarshal(bidRequest.Ext, &reqExt) + if err != nil { + return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error()) + } + } + + if reqExt.Prebid.BidderParams == nil { + return nil, nil + } + + var bidderParams map[string]json.RawMessage + err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams) + if err != nil { + return nil, err + } + + return bidderParams, nil +} + +func ExtractReqExtBidderParams(bidReq *openrtb2.BidRequest) (map[string]map[string]json.RawMessage, error) { + if bidReq == nil { + return nil, errors.New("error bidRequest should not be nil") + } + + reqExt := &openrtb_ext.ExtRequest{} + if len(bidReq.Ext) > 0 { + err := json.Unmarshal(bidReq.Ext, &reqExt) + if err != nil { + return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error()) + } + } + + if reqExt.Prebid.BidderParams == nil { + return nil, nil + } + + var bidderParams map[string]map[string]json.RawMessage + err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams) + if err != nil { + return nil, err + } + + return bidderParams, nil +} diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go new file mode 100644 index 00000000000..e09149a7fc3 --- /dev/null +++ b/adapters/openrtb_util_test.go @@ -0,0 +1,117 @@ +package adapters + +import ( + "encoding/json" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "reflect" + "testing" +) + +func TestExtractAdapterReqBidderParams(t *testing.T) { + type args struct { + bidRequest *openrtb2.BidRequest + } + tests := []struct { + name string + args args + want map[string]json.RawMessage + wantErr bool + }{ + { + name: "extract bidder params from nil req", + args: args{ + bidRequest: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "extract bidder params from nil req.Ext", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)}, + }, + want: nil, + wantErr: false, + }, + { + name: "extract bidder params from req.Ext for input request in adapter code", + args: args{ + bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams": {"profile": 1234, "version": 1}}}`)}, + }, + want: map[string]json.RawMessage{"profile": json.RawMessage(`1234`), "version": json.RawMessage(`1`)}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ExtractAdapterReqBidderParams(tt.args.bidRequest) + if (err != nil) != tt.wantErr { + t.Errorf("ExtractReqExtBidderParams() error = %v, wantErr = %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ExtractReqExtBidderParams() got = %v, want = %v", got, tt.want) + } + }) + } +} + +func TestExtractReqExtBidderParams(t *testing.T) { + type args struct { + request *openrtb2.BidRequest + } + tests := []struct { + name string + args args + want map[string]map[string]json.RawMessage + wantErr bool + }{ + { + name: "extract bidder params from nil req", + args: args{ + request: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "extract bidder params from nil req.Ext.prebid", + args: args{ + request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)}, + }, + want: nil, + wantErr: false, + }, + { + name: "extract bidder params from nil req.Ext", + args: args{ + request: &openrtb2.BidRequest{Ext: nil}, + }, + want: nil, + wantErr: false, + }, + { + name: "extract bidder params from req.Ext for input request before adapter code", + args: args{ + request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile": 1234, "version": 1}, "appnexus": {"key1": 123, "key2": {"innerKey1":"innerValue1"} } }}}`)}, + }, + want: map[string]map[string]json.RawMessage{ + "pubmatic": {"profile": json.RawMessage(`1234`), "version": json.RawMessage(`1`)}, + "appnexus": {"key1": json.RawMessage(`123`), "key2": json.RawMessage(`{"innerKey1":"innerValue1"}`)}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ExtractReqExtBidderParams(tt.args.request) + if (err != nil) != tt.wantErr { + t.Errorf("ExtractReqExtBidderParams() error = %v, wantErr = %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ExtractReqExtBidderParams() got = %v, want = %v", got, tt.want) + } + }) + } +} diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 19024f4a123..799d3364f76 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -26,6 +26,11 @@ type pubmaticBidExt struct { VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` } +type pubmaticWrapperExt struct { + ProfileID int `json:"profile,omitempty"` + VersionID int `json:"version,omitempty"` +} + type pubmaticBidExtVideo struct { Duration *int `json:"duration,omitempty"` } @@ -56,16 +61,51 @@ const ( func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) - wrapExt := "" pubID := "" + var wrapperExt *pubmaticWrapperExt + extractWrapperExtFromImp := true + extractPubIDFromImp := true + + wrapperExt, err := extractPubmaticWrapperExtFromRequest(request) + if err != nil { + return nil, []error{err} + } + if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 { + extractWrapperExtFromImp = false + } for i := 0; i < len(request.Imp); i++ { - err := parseImpressionObject(&request.Imp[i], &wrapExt, &pubID) + wrapperExtFromImp, pubIDFromImp, err := parseImpressionObject(&request.Imp[i], extractWrapperExtFromImp, extractPubIDFromImp) + // 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-- + continue + } + + if extractWrapperExtFromImp { + if wrapperExtFromImp != nil { + if wrapperExt == nil { + wrapperExt = &pubmaticWrapperExt{} + } + if wrapperExt.ProfileID == 0 { + wrapperExt.ProfileID = wrapperExtFromImp.ProfileID + } + if wrapperExt.VersionID == 0 { + wrapperExt.VersionID = wrapperExtFromImp.VersionID + } + + if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 { + extractWrapperExtFromImp = false + } + } + } + + if extractPubIDFromImp && pubIDFromImp != "" { + pubID = pubIDFromImp + extractPubIDFromImp = false } } @@ -74,9 +114,14 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad return nil, errs } - if wrapExt != "" { - rawExt := fmt.Sprintf("{\"wrapper\": %s}", wrapExt) - request.Ext = json.RawMessage(rawExt) + if wrapperExt != nil { + reqExt := make(map[string]interface{}) + reqExt["wrapper"] = wrapperExt + rawExt, err := json.Marshal(reqExt) + if err != nil { + return nil, []error{err} + } + request.Ext = rawExt } if request.Site != nil { @@ -179,10 +224,13 @@ func assignBannerWidthAndHeight(banner *openrtb2.Banner, w, h int64) *openrtb2.B } // parseImpressionObject parse the imp to get it ready to send to pubmatic -func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) error { +func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractPubIDFromImp bool) (*pubmaticWrapperExt, string, error) { + var wrapExt *pubmaticWrapperExt + var pubID string + // PubMatic supports banner and video impressions. if imp.Banner == nil && imp.Video == nil { - return fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID) + return wrapExt, pubID, fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID) } if imp.Audio != nil { @@ -191,36 +239,34 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er var bidderExt ExtImpBidderPubmatic if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return err + return wrapExt, pubID, err } var pubmaticExt openrtb_ext.ExtImpPubmatic if err := json.Unmarshal(bidderExt.Bidder, &pubmaticExt); err != nil { - return err + return wrapExt, pubID, err } - if *pubID == "" { - *pubID = strings.TrimSpace(pubmaticExt.PublisherId) + if extractPubIDFromImp { + pubID = strings.TrimSpace(pubmaticExt.PublisherId) } // Parse Wrapper Extension only once per request - if *wrapExt == "" && len(pubmaticExt.WrapExt) != 0 { - var wrapExtMap map[string]int - err := json.Unmarshal([]byte(pubmaticExt.WrapExt), &wrapExtMap) + if extractWrapperExtFromImp && len(pubmaticExt.WrapExt) != 0 { + err := json.Unmarshal([]byte(pubmaticExt.WrapExt), &wrapExt) if err != nil { - return fmt.Errorf("Error in Wrapper Parameters = %v for ImpID = %v WrapperExt = %v", err.Error(), imp.ID, string(pubmaticExt.WrapExt)) + return wrapExt, pubID, fmt.Errorf("Error in Wrapper Parameters = %v for ImpID = %v WrapperExt = %v", err.Error(), imp.ID, string(pubmaticExt.WrapExt)) } - *wrapExt = string(pubmaticExt.WrapExt) } if err := validateAdSlot(strings.TrimSpace(pubmaticExt.AdSlot), imp); err != nil { - return err + return wrapExt, pubID, err } if imp.Banner != nil { bannerCopy, err := assignBannerSize(imp.Banner) if err != nil { - return err + return wrapExt, pubID, err } imp.Banner = bannerCopy } @@ -253,7 +299,26 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er } } - return nil + return wrapExt, pubID, nil +} + +// extractPubmaticWrapperExtFromRequest parse the imp to get it ready to send to pubmatic +func extractPubmaticWrapperExtFromRequest(request *openrtb2.BidRequest) (*pubmaticWrapperExt, error) { + var wrpExt pubmaticWrapperExt + reqExtBidderParams, err := adapters.ExtractAdapterReqBidderParams(request) + if err != nil { + return nil, err + } + + //get request ext bidder params + if wrapperObj, present := reqExtBidderParams["wrapper"]; present && len(wrapperObj) != 0 { + err = json.Unmarshal(wrapperObj, &wrpExt) + if err != nil { + return nil, err + } + return &wrpExt, nil + } + return nil, nil } func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) { diff --git a/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json b/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json new file mode 100644 index 00000000000..06fc80ae20e --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json @@ -0,0 +1,160 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "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" + }, + "ext": {}, + "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", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "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", + "h": 250, + "w": 300, + "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/pubmatic/pubmatictest/supplemental/reqBidderParams.json b/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json new file mode 100644 index 00000000000..5d4a1819f37 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json @@ -0,0 +1,169 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "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" + }, + "ext": { + "prebid": { + "bidderparams": { + "wrapper": { + "profile": 1234, + "version": 2 + } + } + } + }, + "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", + "tagid": "AdTag_Div1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "h": 250, + "w": 300 + }, + "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": 1234, + "version": 2 + } + } + } + }, + "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", + "h": 250, + "w": 300, + "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/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index ec9e41525a8..10a15b3f8b7 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -23,6 +23,7 @@ import ( nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" "github.com/mxmCherry/openrtb/v15/openrtb2" accountService "github.com/prebid/prebid-server/account" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -290,6 +291,11 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ return } + if err := mergeBidderParams(req); 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.BidRequest) @@ -323,6 +329,117 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio return defaultTimeout } +// mergeBidderParams merges bidder parameters passed at req.ext level with imp[].ext level. +// Preference is given to parameters at imp[].ext level over req.ext level. +// Parameters at req.ext level are propagated to adapters as is without any validation. +func mergeBidderParams(req *openrtb_ext.RequestWrapper) error { + reqBidderParams, err := adapters.ExtractReqExtBidderParams(req.BidRequest) + if err != nil { + return err + } + + impCpy := make([]openrtb2.Imp, 0, len(req.BidRequest.Imp)) + for _, imp := range req.BidRequest.Imp { + updatedImp := imp + + if len(imp.Ext) == 0 { + impCpy = append(impCpy, updatedImp) + continue + } + + var impExt map[string]map[string]json.RawMessage + err := json.Unmarshal(imp.Ext, &impExt) + if err != nil { + return err + } + + //merges bidder parameters passed at req.ext level with imp[].ext level. + err = addMissingReqExtParamsInImpExt(impExt, reqBidderParams) + if err != nil { + return err + } + + //merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level. + err = addMissingReqExtParamsInImpExtPrebid(impExt, reqBidderParams) + if err != nil { + return err + } + + iExt, err := json.Marshal(impExt) + if err != nil { + return fmt.Errorf("error marshalling imp[].ext : %s", err.Error()) + } + updatedImp.Ext = iExt + impCpy = append(impCpy, updatedImp) + } + + req.BidRequest.Imp = impCpy + return nil +} + +// addMissingReqExtParamsInImpExtPrebid merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level. +func addMissingReqExtParamsInImpExtPrebid(impExtBidder map[string]map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) error { + var bidderParams map[string]json.RawMessage + if impExtBidder["prebid"] != nil && impExtBidder["prebid"]["bidder"] != nil { + err := json.Unmarshal(impExtBidder["prebid"]["bidder"], &bidderParams) + if err != nil { + return err + } + } + + if len(bidderParams) != 0 { + for bidder, bidderExt := range bidderParams { + if !isBidderToValidate(bidder) { + continue + } + + var params map[string]json.RawMessage + err := json.Unmarshal(bidderExt, ¶ms) + + for key, value := range reqExtParams[bidder] { + if _, present := params[key]; !present { + params[key] = value + } + } + + paramsJson, err := json.Marshal(params) + if err != nil { + return err + } + bidderParams[bidder] = paramsJson + } + + bidderParamsJson, err := json.Marshal(bidderParams) + if err != nil { + return err + } + impExtBidder["prebid"]["bidder"] = bidderParamsJson + } + + return nil +} + +// addMissingReqExtParamsInImpExt merges bidder parameters passed at req.ext level with imp[].ext level. +func addMissingReqExtParamsInImpExt(impExtBidder map[string]map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) error { + for bidder, bidderExt := range impExtBidder { + if !isBidderToValidate(bidder) { + continue + } + + wasModified := false + for key, value := range reqExtParams[bidder] { + if _, present := bidderExt[key]; !present { + bidderExt[key] = value + wasModified = true + } + } + if wasModified { + impExtBidder[bidder] = bidderExt + } + } + return nil +} + func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []error { errL := []error{} if req.ID == "" { @@ -1077,6 +1194,8 @@ func isBidderToValidate(bidder string) bool { return false case openrtb_ext.BidderReservedSKAdN: return false + case openrtb_ext.BidderReservedBidder: + return false default: return true } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 8439cc65764..67348b57afa 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -3915,3 +3915,117 @@ func TestValidateBanner(t *testing.T) { assert.Equal(t, test.expectedError, result, test.description) } } + +func TestParseRequestMergeBidderParams(t *testing.T) { + type args struct { + reqBody string + } + tests := []struct { + name string + args args + expectedImpExt json.RawMessage + expectedReqExt json.RawMessage + errL int + }{ + { + name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder", + args: args{ + reqBody: validRequest(t, "req-ext-bidder-params.json"), + }, + expectedImpExt: getObject(t, "req-ext-bidder-params.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params.json", "expectedReqExt"), + errL: 0, + }, + { + name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params", + args: args{ + reqBody: validRequest(t, "req-ext-bidder-params-merge.json"), + }, + expectedImpExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedReqExt"), + errL: 0, + }, + { + name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility", + args: args{ + reqBody: validRequest(t, "req-ext-bidder-params-backward-compatible-merge.json"), + }, + expectedImpExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedReqExt"), + errL: 0, + }, + { + name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder for reserved keyword", + args: args{ + reqBody: validRequest(t, "req-ext-bidder-params-merge-reserved-keyword.json"), + }, + expectedImpExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedImpExt"), + expectedReqExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedReqExt"), + errL: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + deps := &endpointDeps{ + fakeUUIDGenerator{}, + &warningsCheckExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(len(tt.args.reqBody))}, + &metricsConfig.NilMetricsEngine{}, + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(tt.args.reqBody)) + + resReq, _, errL := deps.parseRequest(req) + + var expIExt, iExt map[string]interface{} + err := json.Unmarshal(tt.expectedImpExt, &expIExt) + assert.Nil(t, err, "unmarshal() should return nil error") + + assert.NotNil(t, resReq.BidRequest.Imp[0].Ext, "imp[0].Ext should not be nil") + err = json.Unmarshal(resReq.BidRequest.Imp[0].Ext, &iExt) + assert.Nil(t, err, "unmarshal() should return nil error") + + assert.Equal(t, expIExt, iExt, "bidderparams in imp[].Ext should match") + + var eReqE, reqE map[string]interface{} + err = json.Unmarshal(tt.expectedReqExt, &eReqE) + assert.Nil(t, err, "unmarshal() should return nil error") + + err = json.Unmarshal(resReq.BidRequest.Ext, &reqE) + assert.Nil(t, err, "unmarshal() should return nil error") + + assert.Equal(t, eReqE, reqE, "req.Ext should match") + + assert.Len(t, errL, tt.errL, "error length should match") + }) + } +} + +func getObject(t *testing.T, filename, key string) json.RawMessage { + requestData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + filename) + if err != nil { + t.Fatalf("Failed to fetch a valid request: %v", err) + } + testBidRequest, _, _, err := jsonparser.Get(requestData, key) + assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err) + + var obj json.RawMessage + err = json.Unmarshal(testBidRequest, &obj) + if err != nil { + t.Fatalf("Failed to fetch object with key '%s' ... got error: %v", key, err) + } + return obj +} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json index 76dc1c2bb5c..5baaa09d46c 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json @@ -27,5 +27,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n" + "expectedErrorMessage": "Invalid request: error decoding Request.ext : request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json index d9ed59c8c6e..b1e9b7849e2 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json @@ -20,5 +20,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.imp[0].ext.appnexus failed validation.\n(root): Invalid type. Expected: object, given: string\n" + "expectedErrorMessage": "Invalid request: json: cannot unmarshal string into Go value of type map[string]json.RawMessage" } diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json new file mode 100644 index 00000000000..274c2c70f4b --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json @@ -0,0 +1,101 @@ +{ + "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility", + "mockBidRequest": { + "id": "some-request-id", + "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": 111111 + }, + "pubmatic": { + "wrapper": { + "profile": 1234 + } + }, + "prebid": { + "bidder": {} + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + } + }, + "expectedReqExt": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + }, + "expectedImpExt": { + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 1234 + } + }, + "prebid": { + "bidder": {} + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json new file mode 100644 index 00000000000..982f86cb4e0 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json @@ -0,0 +1,116 @@ +{ + "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder for reserved keyword", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "pmp": { + "deals": [ + { + "id": "some-deal-id" + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "bidder": { + "param1": 111111 + }, + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234 + } + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "bidder": { + "param2": 111111 + }, + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + } + }, + "expectedReqExt": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "bidder": { + "param2": 111111 + }, + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + }, + "expectedImpExt": { + "prebid": { + "bidder": { + "bidder": { + "param1": 111111 + }, + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234 + } + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json new file mode 100644 index 00000000000..22026f56ed6 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json @@ -0,0 +1,104 @@ +{ + "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "pmp": { + "deals": [ + { + "id": "some-deal-id" + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234 + } + } + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + } + }, + "expectedReqExt": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 222222 + }, + "pubmatic": { + "publisherId": "5678", + "wrapper": { + "profile": 5678, + "version": 2 + } + } + } + } + }, + "expectedImpExt": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 111111 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234 + } + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json new file mode 100644 index 00000000000..9d4bcd0dd59 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json @@ -0,0 +1,98 @@ +{ + "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder", + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 600 + } + ] + }, + "pmp": { + "deals": [ + { + "id": "some-deal-id" + } + ] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": {}, + "pubmatic": {} + } + } + } + } + ], + "ext": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 12883451 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234, + "version": 2 + } + } + } + } + } + }, + "expectedReqExt": { + "prebid": { + "targeting": { + "pricegranularity": "low" + }, + "cache": { + "bids": {} + }, + "bidderparams": { + "appnexus": { + "placementId": 12883451 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234, + "version": 2 + } + } + } + } + }, + "expectedImpExt": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + }, + "pubmatic": { + "publisherId": "1234", + "wrapper": { + "profile": 1234, + "version": 2 + } + } + } + } + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/exchange/utils.go b/exchange/utils.go index 58a3883602f..23ffe5e5eb6 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -11,6 +11,7 @@ import ( "github.com/prebid/go-gdpr/vendorconsent" "github.com/buger/jsonparser" + "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/firstpartydata" "github.com/prebid/prebid-server/gdpr" @@ -222,6 +223,11 @@ func getAuctionBidderRequests(req AuctionRequest, return nil, []error{err} } + bidderParamsInReqExt, err := adapters.ExtractReqExtBidderParams(req.BidRequest) + if err != nil { + return nil, []error{err} + } + var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain // Quick extra wrapper until RequestWrapper makes its way into CleanRequests @@ -232,21 +238,33 @@ func getAuctionBidderRequests(req AuctionRequest, } } - reqExt, err := getExtJson(req.BidRequest, requestExt) - if err != nil { - return nil, []error{err} - } - var errs []error for bidder, imps := range impsByBidder { coreBidder := resolveBidder(bidder, aliases) reqCopy := *req.BidRequest reqCopy.Imp = imps - reqCopy.Ext = reqExt prepareSource(&reqCopy, bidder, sChainsByBidder) + if len(bidderParamsInReqExt) != 0 { + + // Update bidder-params(requestExt.Prebid.BidderParams) for the bidder to only contain bidder-params for + // this bidder only and remove bidder-params for all other bidders from requestExt.Prebid.BidderParams + params, err := getBidderParamsForBidder(bidderParamsInReqExt, bidder) + if err != nil { + return nil, []error{err} + } + + requestExt.Prebid.BidderParams = params + } + + reqExt, err := getExtJson(req.BidRequest, requestExt) + if err != nil { + return nil, []error{err} + } + reqCopy.Ext = reqExt + if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil { errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err)) continue @@ -278,6 +296,18 @@ func getAuctionBidderRequests(req AuctionRequest, return bidderRequests, errs } +func getBidderParamsForBidder(bidderParamsInReqExt map[string]map[string]json.RawMessage, bidder string) (json.RawMessage, error) { + var params json.RawMessage + if bidderParams, ok := bidderParamsInReqExt[bidder]; ok { + var err error + params, err = json.Marshal(bidderParams) + if err != nil { + return nil, err + } + } + return params, nil +} + func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) { if len(req.Ext) == 0 || unpackedExt == nil { return json.RawMessage(``), nil diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 7c52c88bf7e..d0b4006dd15 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -933,6 +933,90 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { } } +func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { + testCases := []struct { + description string + inExt json.RawMessage + expectedExt map[string]json.RawMessage + hasError bool + }{ + { + description: "Nil Bidder params", + inExt: nil, + expectedExt: getExpectedReqExt(true, false, false), + hasError: false, + }, + { + description: "Bidder params for single partner", + inExt: json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile":1234,"version":2}}}}`), + expectedExt: getExpectedReqExt(false, true, false), + hasError: false, + }, + { + description: "Bidder params for two partners", + inExt: json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile":1234,"version":2}, "appnexus": {"key1": 123, "key2": {"innerKey1":"innerValue1"}} }}}`), + expectedExt: getExpectedReqExt(false, true, true), + hasError: false, + }, + } + + for _, test := range testCases { + req := newBidRequestWithBidderParams(t) + var extRequest *openrtb_ext.ExtRequest + if test.inExt != nil { + req.Ext = test.inExt + unmarshaledExt, err := extractBidRequestExt(req) + assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt") + extRequest = unmarshaledExt + } + + auctionReq := AuctionRequest{ + BidRequest: req, + UserSyncs: &emptyUsersync{}, + } + + bidderToSyncerKey := map[string]string{} + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) + if test.hasError == true { + assert.NotNil(t, errs) + assert.Len(t, bidderRequests, 0) + } else { + assert.Nil(t, errs) + for _, r := range bidderRequests { + expected := test.expectedExt[r.BidderName.String()] + actual := r.BidRequest.Ext + assert.Equal(t, expected, actual, test.description+" Req:Ext.Prebid.BidderParams") + } + } + } +} + +func getExpectedReqExt(nilExt, includePubmaticParams, includeAppnexusParams bool) map[string]json.RawMessage { + bidderParamsMap := make(map[string]json.RawMessage) + + if nilExt { + bidderParamsMap["pubmatic"] = json.RawMessage(``) + bidderParamsMap["appnexus"] = json.RawMessage(``) + return bidderParamsMap + } + + if includePubmaticParams { + bidderParamsMap["pubmatic"] = json.RawMessage(`{"prebid":{"bidderparams":{"profile":1234,"version":2}}}`) + } else { + bidderParamsMap["pubmatic"] = json.RawMessage(`{"prebid":{}}`) + } + + if includeAppnexusParams { + bidderParamsMap["appnexus"] = json.RawMessage(`{"prebid":{"bidderparams":{"key1":123,"key2":{"innerKey1":"innerValue1"}}}}`) + } else { + bidderParamsMap["appnexus"] = json.RawMessage(`{"prebid":{}}`) + } + + return bidderParamsMap +} + func TestExtractBidRequestExt(t *testing.T) { var boolFalse, boolTrue *bool = new(bool), new(bool) *boolFalse = false @@ -2000,6 +2084,47 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { } } +func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest { + return &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Page: "www.some.domain.com", + Domain: "domain.com", + Publisher: &openrtb2.Publisher{ + ID: "some-publisher-id", + }, + }, + Device: &openrtb2.Device{ + DIDMD5: "some device ID hash", + UA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + IFA: "ifa", + IP: "132.173.230.74", + Language: "EN", + }, + Source: &openrtb2.Source{ + TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", + }, + User: &openrtb2.User{ + ID: "our-id", + BuyerUID: "their-id", + Yob: 1982, + Ext: json.RawMessage(`{}`), + }, + Imp: []openrtb2.Imp{{ + ID: "some-imp-id", + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ + W: 300, + H: 250, + }, { + W: 300, + H: 600, + }}, + }, + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "pubmatic":{"publisherId": "1234"}}`), + }}, + } +} + func TestRandomizeList(t *testing.T) { var ( bidder1 = openrtb_ext.BidderName("bidder1") diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index d5c424c5ead..8483ea67186 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -34,6 +34,7 @@ const ( BidderReservedGeneral BidderName = "general" // Reserved for non-bidder specific messages when using a map keyed on the bidder name. BidderReservedPrebid BidderName = "prebid" // Reserved for Prebid Server configuration. BidderReservedSKAdN BidderName = "skadn" // Reserved for Apple's SKAdNetwork OpenRTB extension. + BidderReservedBidder BidderName = "bidder" // Reserved for passing bidder parameters. ) // IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name. @@ -62,6 +63,10 @@ func IsBidderNameReserved(name string) bool { return true } + if strings.EqualFold(name, string(BidderReservedBidder)) { + return true + } + return false } diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index 26ebf7dc74b..8bd25d9e14f 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -110,6 +110,9 @@ func TestIsBidderNameReserved(t *testing.T) { {"prebid", true}, {"PREbid", true}, {"PREBID", true}, + {"bidder", true}, + {"BIDDER", true}, + {"BidDer", true}, {"notreserved", false}, } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index d399715f4d8..a65e8d3c7b1 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -37,6 +37,7 @@ type ExtRequestPrebid struct { StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"` SupportDeals bool `json:"supportdeals,omitempty"` Targeting *ExtRequestTargeting `json:"targeting,omitempty"` + BidderParams json.RawMessage `json:"bidderparams,omitempty"` // NoSale specifies bidders with whom the publisher has a legal relationship where the // passing of personally identifiable information doesn't constitute a sale per CCPA law. From 4d32fe2a55d1756a4e53fd5143bd15004be4ccf8 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 17 Nov 2021 19:40:52 +0100 Subject: [PATCH 137/140] add iframe endpoint for adyoulike sync (#2083) --- static/bidder-info/adyoulike.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml index a8769ddd854..0853cf1061b 100644 --- a/static/bidder-info/adyoulike.yaml +++ b/static/bidder-info/adyoulike.yaml @@ -9,6 +9,10 @@ capabilities: - video - native userSync: + default: iframe + iframe: + url: "http://visitor.omnitagjs.com/visitor/isync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" + userMacro: "[BUYER_USERID]" redirect: url: "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}" userMacro: "[BUYER_USERID]" From 2b72c5efcfcc58e2e47ff849b6563b8fe4c7b43c Mon Sep 17 00:00:00 2001 From: videobyte20 <85643547+videobyte20@users.noreply.github.com> Date: Wed, 17 Nov 2021 20:06:06 +0100 Subject: [PATCH 138/140] New Adapter: VideoByte (#2058) --- adapters/videobyte/params_test.go | 55 ++++ adapters/videobyte/videobyte.go | 158 ++++++++++ adapters/videobyte/videobyte_test.go | 17 ++ .../videobytetest/exemplary/banner.json | 145 +++++++++ .../exemplary/empty-placement-network.json | 149 ++++++++++ .../exemplary/empty-site-domain-ref.json | 143 +++++++++ .../videobytetest/exemplary/multi-format.json | 161 ++++++++++ .../videobytetest/exemplary/multi-imp.json | 277 ++++++++++++++++++ .../videobytetest/exemplary/video.json | 153 ++++++++++ .../supplemental/invalid-imp-ext-bidder.json | 45 +++ .../supplemental/invalid-imp-ext.json | 43 +++ .../supplemental/invalid-response.json | 116 ++++++++ .../supplemental/status-code-bad-request.json | 115 ++++++++ .../supplemental/status-code-no-content.json | 111 +++++++ .../supplemental/status-code-other-error.json | 115 ++++++++ config/config.go | 1 + exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_videobyte.go | 8 + static/bidder-info/videobyte.yaml | 16 + static/bidder-params/videobyte.json | 22 ++ 21 files changed, 1854 insertions(+) create mode 100644 adapters/videobyte/params_test.go create mode 100644 adapters/videobyte/videobyte.go create mode 100644 adapters/videobyte/videobyte_test.go create mode 100644 adapters/videobyte/videobytetest/exemplary/banner.json create mode 100644 adapters/videobyte/videobytetest/exemplary/empty-placement-network.json create mode 100644 adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json create mode 100644 adapters/videobyte/videobytetest/exemplary/multi-format.json create mode 100644 adapters/videobyte/videobytetest/exemplary/multi-imp.json create mode 100644 adapters/videobyte/videobytetest/exemplary/video.json create mode 100644 adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json create mode 100644 adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json create mode 100644 adapters/videobyte/videobytetest/supplemental/invalid-response.json create mode 100644 adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json create mode 100644 adapters/videobyte/videobytetest/supplemental/status-code-no-content.json create mode 100644 adapters/videobyte/videobytetest/supplemental/status-code-other-error.json create mode 100644 openrtb_ext/imp_videobyte.go create mode 100644 static/bidder-info/videobyte.yaml create mode 100644 static/bidder-params/videobyte.json diff --git a/adapters/videobyte/params_test.go b/adapters/videobyte/params_test.go new file mode 100644 index 00000000000..fa38836b03a --- /dev/null +++ b/adapters/videobyte/params_test.go @@ -0,0 +1,55 @@ +package videobyte + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/videobyte.json +// +// These also validate the format of the external API: request.imp[i].ext.videobyte + +// TestValidParams makes sure that the videobyte 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.BidderVideoByte, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected videobyte params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the videobyte 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.BidderVideoByte, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pubId": "123"}`, + `{"pubId": "123", "placementId": "4"}`, + `{"pubId": "123", "nid": "5"}`, + `{"pubId": "123", "placementId": "4", "nid": "5"}`, +} + +var invalidParams = []string{ + `{"invalidParam" : "a"}`, + `{}`, + `{"placementId": "4"}`, + `{"nid": "5"}`, + `{"placementId": "4", "nid": "5"}`, +} diff --git a/adapters/videobyte/videobyte.go b/adapters/videobyte/videobyte.go new file mode 100644 index 00000000000..a4c0df615b8 --- /dev/null +++ b/adapters/videobyte/videobyte.go @@ -0,0 +1,158 @@ +package videobyte + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "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" + + "github.com/mxmCherry/openrtb/v15/openrtb2" +) + +type adapter struct { + endpoint string +} + +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, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + impressions := request.Imp + adapterRequests := make([]*adapters.RequestData, 0, len(impressions)) + var errs []error + + for _, impression := range impressions { + impExt, err := parseExt(&impression) + if err != nil { + errs = append(errs, err) + continue + } + + request.Imp = []openrtb2.Imp{impression} + body, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + continue + } + + adapterRequests = append(adapterRequests, &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.endpoint + "?" + getParams(impExt).Encode(), + Body: body, + Headers: getHeaders(request), + }) + } + + request.Imp = impressions + return adapterRequests, errs +} + +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("Bad user input: HTTP status %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 ortbResponse openrtb2.BidResponse + err := json.Unmarshal(response.Body, &ortbResponse) + if err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + impIdToImp := make(map[string]*openrtb2.Imp) + for i := range internalRequest.Imp { + imp := internalRequest.Imp[i] + impIdToImp[imp.ID] = &imp + } + + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(1) + for _, seatBid := range ortbResponse.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(impIdToImp[bid.ImpID]), + }) + } + } + + return bidderResponse, nil +} + +func getMediaTypeForImp(imp *openrtb2.Imp) openrtb_ext.BidType { + if imp != nil && imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } + return openrtb_ext.BidTypeVideo +} + +func getParams(impExt *openrtb_ext.ExtImpVideoByte) url.Values { + params := url.Values{} + params.Add("source", "pbs") + params.Add("pid", impExt.PublisherId) + if impExt.PlacementId != "" { + params.Add("placementId", impExt.PlacementId) + } + if impExt.NetworkId != "" { + params.Add("nid", impExt.NetworkId) + } + return params +} + +func getHeaders(request *openrtb2.BidRequest) http.Header { + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + if request.Site != nil { + if request.Site.Domain != "" { + headers.Add("Origin", request.Site.Domain) + } + if request.Site.Ref != "" { + headers.Set("Referer", request.Site.Ref) + } + } + return headers +} + +func parseExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpVideoByte, error) { + var bidderExt adapters.ExtImpBidder + + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), + } + } + + impExt := openrtb_ext.ExtImpVideoByte{} + err := json.Unmarshal(bidderExt.Bidder, &impExt) + if err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), + } + } + + return &impExt, nil +} diff --git a/adapters/videobyte/videobyte_test.go b/adapters/videobyte/videobyte_test.go new file mode 100644 index 00000000000..9357a533997 --- /dev/null +++ b/adapters/videobyte/videobyte_test.go @@ -0,0 +1,17 @@ +package videobyte + +import ( + "testing" + + "github.com/prebid/prebid-server/config" + + "github.com/prebid/prebid-server/adapters/adapterstest" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder("videobyte", config.Adapter{Endpoint: "https://mock.videobyte.com"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + adapterstest.RunJSONBidderTest(t, "videobytetest", bidder) +} diff --git a/adapters/videobyte/videobytetest/exemplary/banner.json b/adapters/videobyte/videobytetest/exemplary/banner.json new file mode 100644 index 00000000000..ecf733cca56 --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/banner.json @@ -0,0 +1,145 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json b/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json new file mode 100644 index 00000000000..ddda5aa2a4c --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json @@ -0,0 +1,149 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?pid=examplePublisherId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json b/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json new file mode 100644 index 00000000000..e10a4aa52f4 --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "page": "http://example.com/page-1" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "page": "http://example.com/page-1" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/multi-format.json b/adapters/videobyte/videobytetest/exemplary/multi-format.json new file mode 100644 index 00000000000..cf62ad79dff --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/multi-format.json @@ -0,0 +1,161 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": 300, + "h": 250 + }, + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/multi-imp.json b/adapters/videobyte/videobytetest/exemplary/multi-imp.json new file mode 100644 index 00000000000..547a86da6a2 --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/multi-imp.json @@ -0,0 +1,277 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId-1", + "placementId": "examplePlacementId-1", + "nid": "exampleNetworkId-1" + } + } + }, + { + "id": "test-imp-id-2", + "video": { + "w": 800, + "h": 150, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId-2", + "placementId": "examplePlacementId-2", + "nid": "exampleNetworkId-2" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId-1&pid=examplePublisherId-1&placementId=examplePlacementId-1&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id-1", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId-1", + "placementId": "examplePlacementId-1", + "nid": "exampleNetworkId-1" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id-1", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + }, + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId-2&pid=examplePublisherId-2&placementId=examplePlacementId-2&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id-2", + "video": { + "w": 800, + "h": 150, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId-2", + "placementId": "examplePlacementId-2", + "nid": "exampleNetworkId-2" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23c", + "crid": "72746", + "adomain": [ + "ad-domain.com" + ], + "price": 4, + "impid": "test-imp-id-2", + "adid": "565", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id-1", + "adid": "564", + "adm": "" + }, + "type": "video" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23c", + "crid": "72746", + "adomain": [ + "ad-domain.com" + ], + "price": 4, + "impid": "test-imp-id-2", + "adid": "565", + "adm": "" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/exemplary/video.json b/adapters/videobyte/videobytetest/exemplary/video.json new file mode 100644 index 00000000000..7cb07808020 --- /dev/null +++ b/adapters/videobyte/videobytetest/exemplary/video.json @@ -0,0 +1,153 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + } + ], + "seat": "videobyte" + } + ], + "bidid": "test-request-id", + "id": "test-request-id" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b", + "crid": "72745", + "adomain": [ + "ad-domain.com" + ], + "price": 3, + "impid": "test-imp-id", + "adid": "564", + "adm": "" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json new file mode 100644 index 00000000000..1b806ed57ab --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": "invalid-bidder-ext" + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Ignoring imp id=test-imp-id, error while decoding impExt, err: json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpVideoByte", + "comparison": "literal" + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json new file mode 100644 index 00000000000..199f4c5570c --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json @@ -0,0 +1,43 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": "invalid-ext" + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Ignoring imp id=test-imp-id, error while decoding extImpBidder, err: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder", + "comparison": "literal" + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-response.json b/adapters/videobyte/videobytetest/supplemental/invalid-response.json new file mode 100644 index 00000000000..35bce9a732e --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/invalid-response.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 200, + "body": "invalid-body" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad Server Response", + "comparison": "literal" + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json b/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..09ad1bbb2fc --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad user input: HTTP status 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json b/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..eaa61c74877 --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json @@ -0,0 +1,111 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json b/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..d8a1c1f4034 --- /dev/null +++ b/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "method": "GET", + "headers": { + "Referer": [ + "http://referer.com/page-2" + ], + "Origin": [ + "example.com" + ], + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ] + }, + "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs", + "body": { + "id": "test-request-id", + "bcat": [ + "IAB25", + "IAB7-39", + "IAB8-18" + ], + "user": { + "buyeruid": "user-101", + "yob": 1973 + }, + "device": { + "ua": "my-user-agent", + "ip": "1.2.3.4" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "w": 900, + "h": 250, + "mimes": [ + "video/x-flv", + "video/mp4" + ] + }, + "ext": { + "bidder": { + "pubId": "examplePublisherId", + "placementId": "examplePlacementId", + "nid": "exampleNetworkId" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "http://example.com/page-1", + "ref": "http://referer.com/page-2" + } + } + }, + "mockResponse": { + "status": 505 + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 505. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/config/config.go b/config/config.go index 83585146605..e64562e61a2 100644 --- a/config/config.go +++ b/config/config.go @@ -895,6 +895,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.unruly.endpoint", "https://targeting.unrulymedia.com/unruly_prebid_server") v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") v.SetDefault("adapters.verizonmedia.disabled", true) + v.SetDefault("adapters.videobyte.endpoint", "https://x.videobyte.com/ortbhb") 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:0.1.0") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804") diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 33d662c2ef7..fa1882f3bb4 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -121,6 +121,7 @@ import ( "github.com/prebid/prebid-server/adapters/unicorn" "github.com/prebid/prebid-server/adapters/unruly" "github.com/prebid/prebid-server/adapters/valueimpression" + "github.com/prebid/prebid-server/adapters/videobyte" "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/yahoossp" @@ -261,6 +262,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderUnruly: unruly.Builder, openrtb_ext.BidderValueImpression: valueimpression.Builder, openrtb_ext.BidderVerizonMedia: yahoossp.Builder, + openrtb_ext.BidderVideoByte: videobyte.Builder, openrtb_ext.BidderViewdeos: adtelligent.Builder, openrtb_ext.BidderVisx: visx.Builder, openrtb_ext.BidderVrtcal: vrtcal.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 8483ea67186..3c3f6764e7e 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -200,6 +200,7 @@ const ( BidderUnruly BidderName = "unruly" BidderValueImpression BidderName = "valueimpression" BidderVerizonMedia BidderName = "verizonmedia" + BidderVideoByte BidderName = "videobyte" BidderVisx BidderName = "visx" BidderViewdeos BidderName = "viewdeos" BidderVrtcal BidderName = "vrtcal" @@ -339,6 +340,7 @@ func CoreBidderNames() []BidderName { BidderUnruly, BidderValueImpression, BidderVerizonMedia, + BidderVideoByte, BidderViewdeos, BidderVisx, BidderVrtcal, diff --git a/openrtb_ext/imp_videobyte.go b/openrtb_ext/imp_videobyte.go new file mode 100644 index 00000000000..9101e9f2d4a --- /dev/null +++ b/openrtb_ext/imp_videobyte.go @@ -0,0 +1,8 @@ +package openrtb_ext + +// ExtImpVideoByte defines the contract for bidrequest.imp[i].ext.videobyte +type ExtImpVideoByte struct { + PublisherId string `json:"pubId"` + PlacementId string `json:"placementId"` + NetworkId string `json:"nid"` +} diff --git a/static/bidder-info/videobyte.yaml b/static/bidder-info/videobyte.yaml new file mode 100644 index 00000000000..fc0e767e1af --- /dev/null +++ b/static/bidder-info/videobyte.yaml @@ -0,0 +1,16 @@ +maintainer: + email: "prebid@videobyte.com" +gvlVendorID: 1011 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + redirect: + url: "https://x.videobyte.com/usync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-params/videobyte.json b/static/bidder-params/videobyte.json new file mode 100644 index 00000000000..a9388e26684 --- /dev/null +++ b/static/bidder-params/videobyte.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "VideoByte Adapter Params", + "description": "A schema which validates params accepted by the VideoByte adapter", + + "type": "object", + "properties": { + "pubId": { + "type": "string", + "description": "Publisher ID" + }, + "placementId": { + "type": "string", + "description": "Placement ID" + }, + "nid": { + "type": "string", + "description": "Network ID" + } + }, + "required": ["pubId"] +} From 287cd8e36c297d0f9c865d2d608b453bc6541c58 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Thu, 18 Nov 2021 11:23:26 -0500 Subject: [PATCH 139/140] Currency conversion on adapter JSON tests (#2071) Co-authored-by: Gus Carreon --- adapters/adapterstest/test_json.go | 41 ++++++- .../exemplary/sample_banner.json | 32 ++++- .../supplemental/currency_rate_not_found.json | 45 +++++++ currency/validation.go | 34 +++++ currency/validation_test.go | 116 ++++++++++++++++++ endpoints/openrtb2/auction.go | 28 +---- endpoints/openrtb2/auction_test.go | 107 ---------------- 7 files changed, 264 insertions(+), 139 deletions(-) create mode 100644 adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json create mode 100644 currency/validation.go create mode 100644 currency/validation_test.go diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 1ec2fc08d86..453dccf74eb 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -10,6 +10,8 @@ import ( "github.com/mitchellh/copystructure" "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/stretchr/testify/assert" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" @@ -103,7 +105,40 @@ func loadFile(filename string) (*testSpec, error) { // // More assertions will almost certainly be added in the future, as bugs come up. func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, isAmpTest, isVideoTest bool) { - reqInfo := adapters.ExtraRequestInfo{} + reqInfo := getTestExtraRequestInfo(t, filename, spec, isAmpTest, isVideoTest) + requests := testMakeRequestsImpl(t, filename, spec, bidder, reqInfo) + + testMakeBidsImpl(t, filename, spec, bidder, requests) +} + +// getTestExtraRequestInfo builds the ExtraRequestInfo object that will be passed to testMakeRequestsImpl +func getTestExtraRequestInfo(t *testing.T, filename string, spec *testSpec, isAmpTest, isVideoTest bool) *adapters.ExtraRequestInfo { + t.Helper() + + var reqInfo adapters.ExtraRequestInfo + + // If test request.ext defines its own currency rates, add currency conversion to reqInfo + reqWrapper := &openrtb_ext.RequestWrapper{} + reqWrapper.BidRequest = &spec.BidRequest + + reqExt, err := reqWrapper.GetRequestExt() + assert.NoError(t, err, "Could not unmarshall test request ext. %s", filename) + + reqPrebid := reqExt.GetPrebid() + if reqPrebid != nil && reqPrebid.CurrencyConversions != nil && len(reqPrebid.CurrencyConversions.ConversionRates) > 0 { + err = currency.ValidateCustomRates(reqPrebid.CurrencyConversions) + assert.NoError(t, err, "Error validating currency rates in the test request: %s", filename) + + // Get currency rates conversions from the test request.ext + conversions := currency.NewRates(reqPrebid.CurrencyConversions.ConversionRates) + + // Create return adapters.ExtraRequestInfo object + reqInfo = adapters.NewExtraRequestInfo(conversions) + } else { + reqInfo = adapters.ExtraRequestInfo{} + } + + // Set PbsEntryPoint if either isAmpTest or isVideoTest is true if isAmpTest { // simulates AMP entry point reqInfo.PbsEntryPoint = "amp" @@ -111,9 +146,7 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd reqInfo.PbsEntryPoint = "video" } - requests := testMakeRequestsImpl(t, filename, spec, bidder, &reqInfo) - - testMakeBidsImpl(t, filename, spec, bidder, requests) + return &reqInfo } type testSpec struct { diff --git a/adapters/impactify/impactifytest/exemplary/sample_banner.json b/adapters/impactify/impactifytest/exemplary/sample_banner.json index f5d76c9e4c7..2eeba02b26e 100644 --- a/adapters/impactify/impactifytest/exemplary/sample_banner.json +++ b/adapters/impactify/impactifytest/exemplary/sample_banner.json @@ -14,6 +14,8 @@ } ] }, + "bidfloor": 1.00, + "bidfloorcur": "MXN", "ext":{ "bidder": { "appId": "impactify.io", @@ -22,7 +24,19 @@ } } } - ] + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } }, "httpCalls": [ @@ -45,6 +59,8 @@ } ] }, + "bidfloor": 0.05, + "bidfloorcur": "USD", "ext": { "impactify": { "appId": "impactify.io", @@ -53,7 +69,19 @@ } } } - ] + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } } }, "mockResponse": { diff --git a/adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json b/adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json new file mode 100644 index 00000000000..d52ccd31036 --- /dev/null +++ b/adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "bidfloor": 1.00, + "bidfloorcur": "JPY", + "ext":{ + "bidder": { + "appId": "impactify.io", + "format": "screen", + "style": "impact" + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "MXN": { + "USD": 0.05 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Currency conversion rate not found: 'JPY' => 'USD'", + "comparison": "literal" + } + ] +} diff --git a/currency/validation.go b/currency/validation.go new file mode 100644 index 00000000000..7a0e2aa02bd --- /dev/null +++ b/currency/validation.go @@ -0,0 +1,34 @@ +package currency + +import ( + "fmt" + + "golang.org/x/text/currency" + + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" +) + +// 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 + } + + 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 +} diff --git a/currency/validation_test.go b/currency/validation_test.go new file mode 100644 index 00000000000..d49b9824986 --- /dev/null +++ b/currency/validation_test.go @@ -0,0 +1,116 @@ +package currency + +import ( + "testing" + + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +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) + } +} diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 10a15b3f8b7..2b05e9aa02b 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -26,6 +26,7 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/analytics" "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" @@ -40,7 +41,6 @@ import ( "github.com/prebid/prebid-server/util/iputil" "github.com/prebid/prebid-server/util/uuidutil" "golang.org/x/net/publicsuffix" - "golang.org/x/text/currency" ) const storedRequestTimeoutMillis = 50 @@ -494,7 +494,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []err return []error{err} } - if err := validateCustomRates(reqPrebid.CurrencyConversions); err != nil { + if err := currency.ValidateCustomRates(reqPrebid.CurrencyConversions); err != nil { return []error{err} } } @@ -592,30 +592,6 @@ func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error { return err } -// 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 - } - - 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 diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 67348b57afa..a2f03289a16 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1423,113 +1423,6 @@ 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 From 5e2d890a65d9e6e4278a73a54c5daf988dbb9826 Mon Sep 17 00:00:00 2001 From: Aparna Rao Date: Thu, 18 Nov 2021 13:25:48 -0500 Subject: [PATCH 140/140] 33Across: Enable Support for SRA requests (#2079) --- .devcontainer/devcontainer.json | 1 + adapters/33across/33across.go | 51 +++-- .../exemplary/multi-imp-banner.json | 200 ------------------ static/bidder-info/33across.yaml | 2 +- 4 files changed, 34 insertions(+), 220 deletions(-) delete mode 100644 adapters/33across/33acrosstest/exemplary/multi-imp-banner.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bbb76d3675c..9a7ae4300ca 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,6 +14,7 @@ }, "containerEnv": { "GOPRIVATE": "${localEnv:GOPRIVATE}", + "PBS_GDPR_DEFAULT_VALUE": "0" }, "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index fb329a76f21..b6c16b20fbd 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -54,6 +54,7 @@ type bidTtxExt struct { func (a *TtxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData + var groupedImps = make(map[string][]openrtb2.Imp) // Construct request extension common to all imps // NOTE: not blocking adapter requests on errors @@ -64,27 +65,39 @@ func (a *TtxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapter } request.Ext = reqExt - // Break up multi-imp request into multiple external requests since we don't - // support SRA in our exchange server + // We only support SRA for requests containing same prod and + // zoneID, therefore group all imps accordingly and create a http + // request for each such group for i := 0; i < len(request.Imp); i++ { - if adapterReq, err := a.makeRequest(*request, request.Imp[i]); err == nil { - adapterRequests = append(adapterRequests, adapterReq) + if impCopy, err := makeImps(request.Imp[i]); err == nil { + var impExt Ext + + // Skip over imps whose extensions cannot be read since + // we cannot glean Prod or ZoneID which are required to + // group together. However let's not block request creation. + if err := json.Unmarshal(impCopy.Ext, &impExt); err == nil { + impKey := impExt.Ttx.Prod + impExt.Ttx.Zoneid + groupedImps[impKey] = append(groupedImps[impKey], impCopy) + } else { + errs = append(errs, err) + } } else { errs = append(errs, err) } } + for _, impList := range groupedImps { + if adapterReq, err := a.makeRequest(*request, impList); err == nil { + adapterRequests = append(adapterRequests, adapterReq) + } else { + errs = append(errs, err) + } + } return adapterRequests, errs } -func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) { - impCopy, err := makeImps(imp) - - if err != nil { - return nil, err - } - - request.Imp = []openrtb2.Imp{*impCopy} +func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, impList []openrtb2.Imp) (*adapters.RequestData, error) { + request.Imp = impList // Last Step reqJSON, err := json.Marshal(request) @@ -103,23 +116,23 @@ func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) }, nil } -func makeImps(imp openrtb2.Imp) (*openrtb2.Imp, error) { +func makeImps(imp openrtb2.Imp) (openrtb2.Imp, error) { if imp.Banner == nil && imp.Video == nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: fmt.Sprintf("Imp ID %s must have at least one of [Banner, Video] defined", imp.ID), } } var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } } var ttxExt openrtb_ext.ExtImp33across if err := json.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } } @@ -135,7 +148,7 @@ func makeImps(imp openrtb2.Imp) (*openrtb2.Imp, error) { impExtJSON, err := json.Marshal(impExt) if err != nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } } @@ -149,13 +162,13 @@ func makeImps(imp openrtb2.Imp) (*openrtb2.Imp, error) { imp.Video = videoCopy if err != nil { - return nil, &errortypes.BadInput{ + return openrtb2.Imp{}, &errortypes.BadInput{ Message: err.Error(), } } } - return &imp, nil + return imp, nil } func makeReqExt(request *openrtb2.BidRequest) ([]byte, error) { diff --git a/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json b/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json deleted file mode 100644 index b6d19f55683..00000000000 --- a/adapters/33across/33acrosstest/exemplary/multi-imp-banner.json +++ /dev/null @@ -1,200 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id1", - "banner": { - "format": [{"w": 728, "h": 90}] - }, - "ext": { - "bidder": { - "siteId": "fake-site-id", - "productId": "inview" - } - } - }, - { - "id": "test-imp-id2", - "banner": { - "format": [{"w": 728, "h": 90}] - }, - "ext": { - "bidder": { - "siteId": "fake-site-id", - "productId": "inview" - } - } - } - ], - "site": {} - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://ssc.33across.com", - "body": { - "ext": { - "ttx": { - "caller": [ - { - "name": "Prebid-Server", - "version": "n/a" - } - ] - } - }, - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id1", - "banner": { - "format": [{"w": 728, "h": 90}] - }, - "ext": { - "ttx": { - "prod": "inview", - "zoneid": "fake-site-id" - } - } - } - ], - "site": {} - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "ttx", - "bid": [{ - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id1", - "price": 0.500000, - "adm": "some-test-ad", - "crid": "crid_10", - "h": 90, - "w": 728, - "ext": { - "ttx": { - "mediaType": "banner" - } - } - }] - } - ], - "cur": "USD" - } - } - }, - { - "expectedRequest": { - "uri": "http://ssc.33across.com", - "body": { - "ext": { - "ttx": { - "caller": [ - { - "name": "Prebid-Server", - "version": "n/a" - } - ] - } - }, - "id": "test-request-id", - "imp": [ - { - "id":"test-imp-id2", - "banner": { - "format": [{"w": 728, "h": 90}] - }, - "ext": { - "ttx": { - "prod": "inview", - "zoneid": "fake-site-id" - } - } - } - ], - "site": {} - } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "ttx", - "bid": [{ - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id2", - "price": 0.600000, - "adm": "some-test-ad", - "crid": "crid_10", - "h": 90, - "w": 728, - "ext": { - "ttx": { - "mediaType": "banner" - } - } - }] - } - ], - "cur": "USD" - } - } - } - ], - - "expectedBidResponses": [ - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id1", - "price": 0.5, - "adm": "some-test-ad", - "crid": "crid_10", - "w": 728, - "h": 90, - "ext": { - "ttx": { - "mediaType": "banner" - } - } - }, - "type": "banner" - } - ] - }, - { - "currency": "USD", - "bids": [ - { - "bid": { - "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", - "impid": "test-imp-id2", - "price": 0.6, - "adm": "some-test-ad", - "crid": "crid_10", - "w": 728, - "h": 90, - "ext": { - "ttx": { - "mediaType": "banner" - } - } - }, - "type": "banner" - } - ] - } - ] -} diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index 1ee5366f969..bdda5a7e5a6 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -8,5 +8,5 @@ capabilities: - video userSync: iframe: - url: "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru={{.RedirectURL}}&id=zzz000000000002zzz" + url: "https://ssc-cms.33across.com/ps/?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru={{.RedirectURL}}&id=zzz000000000002zzz" userMacro: "33XUSERID33X" \ No newline at end of file